commit 64a3dfd9e849009649aa16470e8687b601444f62 Author: yumoqing Date: Fri Aug 7 11:23:19 2026 +0800 init: pcc module skeleton — models, CRUD, API, load_path diff --git a/json/cluster_list.json b/json/cluster_list.json new file mode 100644 index 0000000..1d11645 --- /dev/null +++ b/json/cluster_list.json @@ -0,0 +1,47 @@ +{ + "tblname": "cluster", + "alias": "cluster_list", + "title": "集群管理", + "params": { + "browserfields": { + "name": { + "title": "集群名称", + "width": 150 + }, + "cluster_type": { + "title": "集群类型", + "width": 100 + }, + "pool_id": { + "title": "算力池", + "width": 150 + }, + "version": { + "title": "版本", + "width": 100 + }, + "endpoint": { + "title": "入口", + "width": 200 + }, + "status": { + "title": "状态", + "width": 100 + } + }, + "editexclouded": [ + "id", + "resellerid", + "created_at", + "updated_at" + ], + "toolbar": { + "tools": [] + }, + "binds": [], + "new_data_url": "{{entire_url('/pcc/api/cluster_create.dspy')}}", + "update_data_url": "{{entire_url('/pcc/api/cluster_update.dspy')}}", + "delete_data_url": "{{entire_url('/pcc/api/cluster_delete.dspy')}}", + "logined_userorgid": "resellerid" + } +} \ No newline at end of file diff --git a/json/cluster_node_list.json b/json/cluster_node_list.json new file mode 100644 index 0000000..6c68bb8 --- /dev/null +++ b/json/cluster_node_list.json @@ -0,0 +1,39 @@ +{ + "tblname": "cluster_node", + "alias": "cluster_node_list", + "title": "节点分配", + "params": { + "browserfields": { + "cluster_id": { + "title": "集群", + "width": 150 + }, + "node_id": { + "title": "节点", + "width": 150 + }, + "role": { + "title": "角色", + "width": 100 + }, + "status": { + "title": "状态", + "width": 100 + } + }, + "editexclouded": [ + "id", + "resellerid", + "created_at", + "updated_at" + ], + "toolbar": { + "tools": [] + }, + "binds": [], + "new_data_url": "{{entire_url('/cluster_node/api/cluster_node_create.dspy')}}", + "update_data_url": "{{entire_url('/cluster_node/api/cluster_node_update.dspy')}}", + "delete_data_url": "{{entire_url('/cluster_node/api/cluster_node_delete.dspy')}}", + "logined_userorgid": "resellerid" + } +} \ No newline at end of file diff --git a/models/cluster.json b/models/cluster.json new file mode 100644 index 0000000..c878e21 --- /dev/null +++ b/models/cluster.json @@ -0,0 +1,116 @@ +{ + "summary": [ + { + "name": "cluster", + "title": "集群", + "primary": [ + "id" + ], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "商户机构id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "pool_id", + "title": "所属算力池", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "集群名称", + "type": "str", + "length": 128, + "nullable": "no" + }, + { + "name": "cluster_type", + "title": "集群类型(k8s/slurm/ray)", + "type": "char", + "length": 16, + "nullable": "no" + }, + { + "name": "version", + "title": "版本号", + "type": "str", + "length": 64 + }, + { + "name": "endpoint", + "title": "API入口地址", + "type": "str", + "length": 256 + }, + { + "name": "control_config", + "title": "控制节点配置(JSON)", + "type": "text" + }, + { + "name": "compute_config", + "title": "算力节点配置(JSON)", + "type": "text" + }, + { + "name": "status", + "title": "状态(deploying/running/failed/stopped/destroyed)", + "type": "char", + "length": 16, + "default": "deploying" + }, + { + "name": "deploy_log", + "title": "部署日志(JSON)", + "type": "text" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "nullable": "no" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "nullable": "no" + } + ], + "codes": [ + { + "field": "pool_id", + "table": "compute_pool", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "cluster_type", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='cluster_type'" + }, + { + "field": "status", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='cluster_status'" + } + ] +} \ No newline at end of file diff --git a/models/cluster_node.json b/models/cluster_node.json new file mode 100644 index 0000000..04ac8b0 --- /dev/null +++ b/models/cluster_node.json @@ -0,0 +1,83 @@ +{ + "summary": [ + { + "name": "cluster_node", + "title": "集群节点分配", + "primary": [ + "id" + ], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "cluster_id", + "title": "所属集群", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "node_id", + "title": "算力节点", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "role", + "title": "角色(control/compute/storage)", + "type": "char", + "length": 16, + "nullable": "no" + }, + { + "name": "status", + "title": "状态(joining/active/draining/removed)", + "type": "char", + "length": 16, + "default": "joining" + }, + { + "name": "assigned_at", + "title": "分配时间", + "type": "timestamp", + "nullable": "no" + } + ], + "codes": [ + { + "field": "cluster_id", + "table": "cluster", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "node_id", + "table": "compute_node", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "role", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='node_role'" + }, + { + "field": "status", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='node_status'" + } + ] +} \ No newline at end of file diff --git a/pcc/__init__.py b/pcc/__init__.py new file mode 100644 index 0000000..fc2430c --- /dev/null +++ b/pcc/__init__.py @@ -0,0 +1,211 @@ +""" +pcc — 统一集群管理 (Pooled Computing Cluster) + 生命周期: deploy → run → stop → start → destroy + 插件: k8s_plugin, slurm_plugin, ray_plugin +""" +import datetime, json +from appPublic.uniqueID import getID +from sqlor.dbpools import DBPools + +MODULE_NAME = 'pccs' + +_PLUGINS = { + 'k8s': 'pcc.k8s_plugin', + 'slurm': 'pcc.slurm_plugin', + 'ray': 'pcc.ray_plugin', +} + + +def _get_plugin(cluster_type): + import importlib + modname = _PLUGINS.get(cluster_type) + if not modname: + raise ValueError(f'Unknown cluster_type: {cluster_type}') + return importlib.import_module(modname) + + +async def deploy_cluster(request, cluster_id): + """部署集群: 读取集群配置 → 分配节点 → 调用 plugin 部署""" + env = request._run_ns + dbname = env.get_module_dbname(MODULE_NAME) + + async with DBPools().sqlorContext(dbname) as sor: + recs = await sor.R('cluster', {'id': cluster_id}) + if not recs: + return {'status': 'error', 'message': f'Cluster {cluster_id} not found'} + cluster = recs[0] + + # 更新状态为 deploying + await sor.U('cluster', {'id': cluster_id}, + {'status': 'deploying', 'updated_at': datetime.datetime.now().isoformat()}) + + # 获取已分配的节点 + node_recs = await sor.R('cluster_node', {'cluster_id': cluster_id}) + control_nodes = [await sor.R('compute_node', {'id': n.node_id}) for n in node_recs if n.role == 'control'] + compute_nodes = [await sor.R('compute_node', {'id': n.node_id}) for n in node_recs if n.role == 'compute'] + + control_nodes = [n[0] for n in control_nodes if n] + compute_nodes = [n[0] for n in compute_nodes if n] + + if not control_nodes: + return {'status': 'error', 'message': 'No control node assigned'} + + config = {} + if cluster.control_config: + try: + config = json.loads(cluster.control_config) + except: + pass + + # 调用对应 plugin 部署 + try: + plugin = _get_plugin(cluster.cluster_type) + deploy_log = await plugin.deploy_cluster(env, cluster_id, control_nodes, compute_nodes, config) + except Exception as e: + async with DBPools().sqlorContext(dbname) as sor: + await sor.U('cluster', {'id': cluster_id}, + {'status': 'failed', 'deploy_log': str(e), + 'updated_at': datetime.datetime.now().isoformat()}) + return {'status': 'error', 'message': f'Deploy failed: {e}'} + + # 更新状态为 running + endpoint = deploy_log.get('endpoint', '') if isinstance(deploy_log, dict) else '' + async with DBPools().sqlorContext(dbname) as sor: + await sor.U('cluster', {'id': cluster_id}, + {'status': 'running', 'endpoint': endpoint, + 'deploy_log': json.dumps(deploy_log) if isinstance(deploy_log, dict) else str(deploy_log), + 'updated_at': datetime.datetime.now().isoformat()}) + # 更新节点状态为 active + for n in control_nodes + compute_nodes: + await sor.U('cluster_node', {'cluster_id': cluster_id, 'node_id': n.id}, + {'status': 'active'}) + + return {'status': 'ok', 'message': 'Cluster deployed', 'endpoint': endpoint} + + +async def add_cluster_node(request, params_kw): + """向运行中集群动态添加节点""" + env = request._run_ns + dbname = env.get_module_dbname(MODULE_NAME) + cluster_id = params_kw.get('cluster_id', '') + node_id = params_kw.get('node_id', '') + role = params_kw.get('role', 'compute') + + async with DBPools().sqlorContext(dbname) as sor: + cluster = await sor.R('cluster', {'id': cluster_id}) + if not cluster: + return {'status': 'error', 'message': 'Cluster not found'} + cluster = cluster[0] + + node = await sor.R('compute_node', {'id': node_id}) + if not node: + return {'status': 'error', 'message': 'Node not found'} + node = node[0] + + # 更新节点状态 + await sor.U('compute_node', {'id': node_id}, + {'status': 'allocated', 'updated_at': datetime.datetime.now().isoformat()}) + + # 记录分配 + await sor.C('cluster_node', { + 'id': getID(), 'cluster_id': cluster_id, 'node_id': node_id, + 'role': role, 'status': 'joining', + 'assigned_at': datetime.datetime.now().isoformat() + }) + + # 调用 plugin 添加 + plugin = _get_plugin(cluster.cluster_type) + result = await plugin.add_node(env, cluster_id, node_id, role) + return result + + +async def remove_cluster_node(request, params_kw): + """从集群移除节点并回收""" + env = request._run_ns + dbname = env.get_module_dbname(MODULE_NAME) + cluster_id = params_kw.get('cluster_id', '') + node_id = params_kw.get('node_id', '') + + async with DBPools().sqlorContext(dbname) as sor: + cluster = await sor.R('cluster', {'id': cluster_id}) + if not cluster: + return {'status': 'error', 'message': 'Cluster not found'} + + plugin = _get_plugin(cluster[0].cluster_type) + result = await plugin.remove_node(env, cluster_id, node_id) + + # 回收节点 + async with DBPools().sqlorContext(dbname) as sor: + await sor.U('compute_node', {'id': node_id}, + {'status': 'available', 'updated_at': datetime.datetime.now().isoformat()}) + await sor.U('cluster_node', {'cluster_id': cluster_id, 'node_id': node_id}, + {'status': 'removed'}) + + return result + + +async def cluster_status(request, cluster_id): + """获取集群运行状态""" + env = request._run_ns + dbname = env.get_module_dbname(MODULE_NAME) + + async with DBPools().sqlorContext(dbname) as sor: + cluster = await sor.R('cluster', {'id': cluster_id}) + if not cluster: + return {'status': 'error', 'message': 'Cluster not found'} + cluster = cluster[0] + nodes = await sor.R('cluster_node', {'cluster_id': cluster_id}) + + # 从 plugin 获取实时状态 + try: + plugin = _get_plugin(cluster.cluster_type) + live_status = await plugin.cluster_status(env, cluster_id) + except: + live_status = {} + + return { + 'status': 'ok', + 'data': { + 'id': cluster.id, 'name': cluster.name, 'type': cluster.cluster_type, + 'version': cluster.version, 'endpoint': cluster.endpoint, + 'state': cluster.status, + 'nodes': [{'id': n.node_id, 'role': n.role, 'status': n.status} for n in nodes], + 'live': live_status + } + } + + +async def stop_cluster(request, cluster_id): + """停止集群""" + env = request._run_ns + dbname = env.get_module_dbname(MODULE_NAME) + async with DBPools().sqlorContext(dbname) as sor: + await sor.U('cluster', {'id': cluster_id}, + {'status': 'stopped', 'updated_at': datetime.datetime.now().isoformat()}) + return {'status': 'ok', 'message': 'Cluster stopped'} + + +async def start_cluster(request, cluster_id): + """启动已停止的集群""" + env = request._run_ns + dbname = env.get_module_dbname(MODULE_NAME) + async with DBPools().sqlorContext(dbname) as sor: + await sor.U('cluster', {'id': cluster_id}, + {'status': 'running', 'updated_at': datetime.datetime.now().isoformat()}) + return {'status': 'ok', 'message': 'Cluster started'} + + +async def destroy_cluster(request, cluster_id): + """销毁集群,回收所有节点""" + env = request._run_ns + dbname = env.get_module_dbname(MODULE_NAME) + async with DBPools().sqlorContext(dbname) as sor: + # 释放所有节点 + nodes = await sor.R('cluster_node', {'cluster_id': cluster_id}) + for n in nodes: + await sor.U('compute_node', {'id': n.node_id}, + {'status': 'available', 'updated_at': datetime.datetime.now().isoformat()}) + await sor.U('cluster_node', {'id': n.id}, {'status': 'removed'}) + await sor.U('cluster', {'id': cluster_id}, + {'status': 'destroyed', 'updated_at': datetime.datetime.now().isoformat()}) + return {'status': 'ok', 'message': 'Cluster destroyed'} diff --git a/pcc/k8s_plugin/__init__.py b/pcc/k8s_plugin/__init__.py new file mode 100644 index 0000000..6e99b5d --- /dev/null +++ b/pcc/k8s_plugin/__init__.py @@ -0,0 +1,24 @@ +''' +k8s_plugin - K8s + VM 集群管理 +- K8s 部署 (kubeadm/k3s + 控制平面 + worker) +- VM 生命周期 (创建/快照/迁移/销毁) +- 节点加入/退出集群 +- kubectl / k8s API 状态监控 +- 算力单元: namespace + ResourceQuota 分配回收 +''') + +async def deploy_k8s_cluster(env, cluster_id, control_nodes, compute_nodes, config): + '''部署 K8s 集群: 初始化控制平面 → join worker → 验证''' + pass + +async def add_node(env, cluster_id, node_id, role='compute'): + '''向已有集群添加节点''' + pass + +async def remove_node(env, cluster_id, node_id): + '''从集群移除节点并回收资源''' + pass + +async def cluster_status(env, cluster_id): + '''获取集群状态 (节点数、Pod数、资源使用率)''' + pass diff --git a/pcc/ray_plugin/__init__.py b/pcc/ray_plugin/__init__.py new file mode 100644 index 0000000..1bff9f6 --- /dev/null +++ b/pcc/ray_plugin/__init__.py @@ -0,0 +1,23 @@ +''' +ray_plugin - Ray 集群管理 +- Ray 集群部署 (ray start --head / --worker) +- 自动扩缩容 (Ray Autoscaler) +- Dashboard + Prometheus 监控 +- 算力单元: Ray resource 标签分配回收 +''' + +async def deploy_ray_cluster(env, cluster_id, control_nodes, compute_nodes, config): + '''部署 Ray 集群: head node → worker nodes''' + pass + +async def add_node(env, cluster_id, node_id, role='compute'): + '''向 Ray 集群添加 worker''' + pass + +async def remove_node(env, cluster_id, node_id): + '''从 Ray 集群移除 worker''' + pass + +async def cluster_status(env, cluster_id): + '''Ray Dashboard API 状态''' + pass diff --git a/pcc/slurm_plugin/__init__.py b/pcc/slurm_plugin/__init__.py new file mode 100644 index 0000000..a880993 --- /dev/null +++ b/pcc/slurm_plugin/__init__.py @@ -0,0 +1,24 @@ +''' +slurm_plugin - Slurm 集群管理 +- Slurm 部署 (slurmctld + slurmd + munge + 共享存储) +- Partition 管理 / QoS / TRES +- 节点 drain/resume (算力回收/分配) +- sinfo / squeue / scontrol 监控 +''' +'') + +async def deploy_slurm_cluster(env, cluster_id, control_nodes, compute_nodes, config): + '''部署 Slurm 集群: 控制节点 slurmctld + munge, 算力节点 slurmd''' + pass + +async def add_node(env, cluster_id, node_id, role='compute'): + '''向分区添加节点''' + pass + +async def remove_node(env, cluster_id, node_id): + '''drain 节点后从集群移除''' + pass + +async def cluster_status(env, cluster_id): + '''sinfo + squeue 汇总''' + pass diff --git a/scripts/load_path.py b/scripts/load_path.py new file mode 100644 index 0000000..59b37c6 --- /dev/null +++ b/scripts/load_path.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""pcc RBAC 权限管理""" +import subprocess, os, sys, json, glob + +def find_sage_root(): + for c in [os.path.expanduser("~/sage"), os.path.expanduser("~/repos/sage")]: + if os.path.isdir(os.path.join(c, "py3")): return c + return None + +SAGE = find_sage_root() +if not SAGE: sys.exit("Sage root not found") +PY = os.path.join(SAGE, "py3", "bin", "python") +SET = os.path.join(SAGE, "set_role_perm.py") + +JSON_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "json") +API_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "wwwroot", "api") + +def load_cruds(): + defs = [] + for f in sorted(glob.glob(os.path.join(JSON_DIR, "*.json"))): + try: + d = json.load(open(f)) + defs.append({"tblname": d["tblname"], "alias": d.get("alias", d["tblname"]), "subtables": d.get("params", {}).get("subtables", [])}) + except: pass + return defs + +def get_apis(): + if not os.path.isdir(API_DIR): return [] + return [f"/pcc/api/{f}" for f in sorted(os.listdir(API_DIR)) if f.endswith(".dspy")] + +cruds = load_cruds() +apis = get_apis() + +PATHS_ANY = [ + f"/pcc/menu.ui", +] +PATHS_LOGINED = [ + f"/pcc", + f"/pcc/index.ui", +] +for d in cruds: + PATHS_ANY.append(f"/pcc/{d['alias']}") + PATHS_LOGINED.append(f"/pcc/{d['alias']}/index.ui") + for act in ["get", "add", "update", "delete"]: + PATHS_LOGINED.append(f"/pcc/{d['alias']}/{act}_{d['tblname']}.dspy") +for api in apis: + PATHS_LOGINED.append(api) + +PATHS_OPERATOR = list(PATHS_LOGINED) + +PATHS_ANY = list(dict.fromkeys(PATHS_ANY)) +PATHS_LOGINED = list(dict.fromkeys(PATHS_LOGINED)) +PATHS_OPERATOR = list(dict.fromkeys(PATHS_OPERATOR)) + +def reg(role, paths): + ok = 0 + for p in paths: + r = subprocess.run([PY, SET, role, p], capture_output=True, text=True) + if r.returncode == 0: ok += 1 + print(f" {role}: {ok}/{len(paths)}") + return ok + +total = 0 +print(f"{mod_name}: any={len(PATHS_ANY)} logined={len(PATHS_LOGINED)} operator={len(PATHS_OPERATOR)}") +total += reg("any", PATHS_ANY) +total += reg("logined", PATHS_LOGINED) +total += reg("reseller.operator", PATHS_OPERATOR) +print(f"Done. {total} entries.") diff --git a/setup.json b/setup.json new file mode 100644 index 0000000..d5eb219 --- /dev/null +++ b/setup.json @@ -0,0 +1,8 @@ +{ + "name": "pcc", + "version": "0.1.0", + "description": "统一集群管理:K8s/Slurm/Ray 集群生命周期、节点分配、动态维护、监控", + "packages": ["pcc", "pcc.k8s_plugin", "pcc.slurm_plugin", "pcc.ray_plugin"], + "install_requires": ["apppublic", "sqlor", "ahserver", "appbase", "rbac"], + "python_requires": ">=3.10" +} \ No newline at end of file diff --git a/wwwroot/api/cluster_add_node.dspy b/wwwroot/api/cluster_add_node.dspy new file mode 100644 index 0000000..91a6cc6 --- /dev/null +++ b/wwwroot/api/cluster_add_node.dspy @@ -0,0 +1,3 @@ +# 向运行中集群动态添加节点 +result = await add_cluster_node(request, params_kw) +return result if isinstance(result, dict) else {'widgettype': 'Error', 'options': {'title': '失败', 'message': str(result)}} diff --git a/wwwroot/api/cluster_create.dspy b/wwwroot/api/cluster_create.dspy new file mode 100644 index 0000000..09ccce7 --- /dev/null +++ b/wwwroot/api/cluster_create.dspy @@ -0,0 +1,3 @@ +# cluster create +result = {'status': 'ok', 'message': 'cluster created'} +return result diff --git a/wwwroot/api/cluster_delete.dspy b/wwwroot/api/cluster_delete.dspy new file mode 100644 index 0000000..85ba94d --- /dev/null +++ b/wwwroot/api/cluster_delete.dspy @@ -0,0 +1,3 @@ +# cluster delete +result = {'status': 'ok', 'message': 'cluster deleted'} +return result diff --git a/wwwroot/api/cluster_deploy.dspy b/wwwroot/api/cluster_deploy.dspy new file mode 100644 index 0000000..deb0a9d --- /dev/null +++ b/wwwroot/api/cluster_deploy.dspy @@ -0,0 +1,7 @@ +# 部署集群:分配控制节点 + 算力节点 → 调用对应 plugin 部署 +cluster_id = params_kw.get('cluster_id', '') +if not cluster_id: + return {'widgettype': 'Error', 'options': {'title': '失败', 'message': '缺少集群ID'}} + +result = await deploy_cluster(request, cluster_id) +return result if isinstance(result, dict) else {'widgettype': 'Error', 'options': {'title': '失败', 'message': str(result)}} diff --git a/wwwroot/api/cluster_destroy.dspy b/wwwroot/api/cluster_destroy.dspy new file mode 100644 index 0000000..a69d5d9 --- /dev/null +++ b/wwwroot/api/cluster_destroy.dspy @@ -0,0 +1,4 @@ +# 销毁集群并回收所有节点 +cluster_id = params_kw.get('cluster_id', '') +result = await destroy_cluster(request, cluster_id) +return result diff --git a/wwwroot/api/cluster_node_create.dspy b/wwwroot/api/cluster_node_create.dspy new file mode 100644 index 0000000..4439bb3 --- /dev/null +++ b/wwwroot/api/cluster_node_create.dspy @@ -0,0 +1,3 @@ +# cluster_node create +result = {'status': 'ok', 'message': 'cluster_node created'} +return result diff --git a/wwwroot/api/cluster_node_delete.dspy b/wwwroot/api/cluster_node_delete.dspy new file mode 100644 index 0000000..1b213e6 --- /dev/null +++ b/wwwroot/api/cluster_node_delete.dspy @@ -0,0 +1,3 @@ +# cluster_node delete +result = {'status': 'ok', 'message': 'cluster_node deleted'} +return result diff --git a/wwwroot/api/cluster_node_update.dspy b/wwwroot/api/cluster_node_update.dspy new file mode 100644 index 0000000..9465c15 --- /dev/null +++ b/wwwroot/api/cluster_node_update.dspy @@ -0,0 +1,3 @@ +# cluster_node update +result = {'status': 'ok', 'message': 'cluster_node updated'} +return result diff --git a/wwwroot/api/cluster_remove_node.dspy b/wwwroot/api/cluster_remove_node.dspy new file mode 100644 index 0000000..363f3a1 --- /dev/null +++ b/wwwroot/api/cluster_remove_node.dspy @@ -0,0 +1,3 @@ +# 从集群移除节点 +result = await remove_cluster_node(request, params_kw) +return result if isinstance(result, dict) else {'widgettype': 'Error', 'options': {'title': '失败', 'message': str(result)}} diff --git a/wwwroot/api/cluster_start.dspy b/wwwroot/api/cluster_start.dspy new file mode 100644 index 0000000..4098a2f --- /dev/null +++ b/wwwroot/api/cluster_start.dspy @@ -0,0 +1,4 @@ +# 启动集群 +cluster_id = params_kw.get('cluster_id', '') +result = await start_cluster(request, cluster_id) +return result diff --git a/wwwroot/api/cluster_status.dspy b/wwwroot/api/cluster_status.dspy new file mode 100644 index 0000000..60b1bd3 --- /dev/null +++ b/wwwroot/api/cluster_status.dspy @@ -0,0 +1,6 @@ +# 获取集群运行状态 +cluster_id = params_kw.get('cluster_id', '') +if not cluster_id: + return {'status': 'error', 'message': 'Missing cluster_id'} +result = await cluster_status(request, cluster_id) +return result diff --git a/wwwroot/api/cluster_stop.dspy b/wwwroot/api/cluster_stop.dspy new file mode 100644 index 0000000..3c9e858 --- /dev/null +++ b/wwwroot/api/cluster_stop.dspy @@ -0,0 +1,4 @@ +# 停止集群 +cluster_id = params_kw.get('cluster_id', '') +result = await stop_cluster(request, cluster_id) +return result diff --git a/wwwroot/api/cluster_update.dspy b/wwwroot/api/cluster_update.dspy new file mode 100644 index 0000000..7928592 --- /dev/null +++ b/wwwroot/api/cluster_update.dspy @@ -0,0 +1,3 @@ +# cluster update +result = {'status': 'ok', 'message': 'cluster updated'} +return result