feat: menu/i18n/CRUD/permission/stats/model fixes
This commit is contained in:
parent
56726bc679
commit
2e621267bf
149
build/lib/build/lib/build/lib/pcpool/__init__.py
Normal file
149
build/lib/build/lib/build/lib/pcpool/__init__.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
"""
|
||||||
|
pcpool — 算力中心算力池管理
|
||||||
|
- 算力节点池 CRUD
|
||||||
|
- 节点注册/注销/心跳
|
||||||
|
- 算力单元分配和回收
|
||||||
|
- 池资源统计
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
MODULE_NAME = 'pccs'
|
||||||
|
|
||||||
|
|
||||||
|
async def node_heartbeat(request, params_kw):
|
||||||
|
"""节点心跳上报,更新 last_heartbeat"""
|
||||||
|
env = request._run_ns
|
||||||
|
node_id = params_kw.get('id') or params_kw.get('node_id', '')
|
||||||
|
if not node_id:
|
||||||
|
return {'status': 'error', 'message': 'Missing node id'}
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
now = datetime.datetime.now().isoformat()
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
await sor.U('compute_node', {'id': node_id},
|
||||||
|
{'last_heartbeat': now, 'updated_at': now})
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
|
||||||
|
async def allocate_nodes(request, params_kw):
|
||||||
|
"""从算力池中分配节点给集群。
|
||||||
|
params: pool_id, cluster_id, role (control/compute), count, cpu, memory, gpu
|
||||||
|
返回分配的节点 ID 列表。"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
cluster_id = params_kw.get('cluster_id', '')
|
||||||
|
role = params_kw.get('role', 'compute')
|
||||||
|
count = int(params_kw.get('count', 1))
|
||||||
|
req_cpu = int(params_kw.get('cpu', 0))
|
||||||
|
req_mem = int(params_kw.get('memory', 0))
|
||||||
|
req_gpu = int(params_kw.get('gpu', 0))
|
||||||
|
|
||||||
|
if not pool_id:
|
||||||
|
return {'status': 'error', 'message': 'Missing pool_id'}
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
# 查询可用节点
|
||||||
|
sql = """SELECT * FROM compute_node
|
||||||
|
WHERE pool_id = ${pool_id}$ AND status = 'available'
|
||||||
|
ORDER BY cpu_cores DESC, memory_gb DESC
|
||||||
|
LIMIT ${limit}$"""
|
||||||
|
available = await sor.sqlExe(sql, {'pool_id': pool_id, 'limit': count})
|
||||||
|
if len(available) < count:
|
||||||
|
return {'status': 'error',
|
||||||
|
'message': f'可用节点不足: 需要{count}, 可用{len(available)}'}
|
||||||
|
|
||||||
|
allocated = []
|
||||||
|
for node in available[:count]:
|
||||||
|
await sor.U('compute_node', {'id': node.id},
|
||||||
|
{'status': 'allocated', 'updated_at': datetime.datetime.now().isoformat()})
|
||||||
|
# 记录分配关系
|
||||||
|
cn_id = getID()
|
||||||
|
await sor.C('cluster_node', {
|
||||||
|
'id': cn_id, 'cluster_id': cluster_id,
|
||||||
|
'node_id': node.id, 'role': role, 'status': 'joining',
|
||||||
|
'assigned_at': datetime.datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
allocated.append(node.id)
|
||||||
|
|
||||||
|
# 更新池已分配资源
|
||||||
|
if allocated:
|
||||||
|
await sor.sqlExe(
|
||||||
|
"""UPDATE compute_pool
|
||||||
|
SET allocated_cpu = allocated_cpu + ${cpu}$,
|
||||||
|
allocated_memory_gb = allocated_memory_gb + ${mem}$,
|
||||||
|
allocated_gpu = allocated_gpu + ${gpu}$,
|
||||||
|
updated_at = ${now}$
|
||||||
|
WHERE id = ${pool_id}$""",
|
||||||
|
{'cpu': req_cpu * len(allocated), 'mem': req_mem * len(allocated),
|
||||||
|
'gpu': req_gpu * len(allocated), 'pool_id': pool_id,
|
||||||
|
'now': datetime.datetime.now().isoformat()}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {'status': 'ok', 'allocated_nodes': allocated}
|
||||||
|
|
||||||
|
|
||||||
|
async def release_nodes(request, params_kw):
|
||||||
|
"""回收节点回算力池。
|
||||||
|
params: node_ids (逗号分隔), pool_id"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
node_ids_str = params_kw.get('node_ids', '')
|
||||||
|
if not node_ids_str:
|
||||||
|
return {'status': 'error', 'message': 'Missing node_ids'}
|
||||||
|
node_ids = [n.strip() for n in node_ids_str.split(',') if n.strip()]
|
||||||
|
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
now = datetime.datetime.now().isoformat()
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
for nid in node_ids:
|
||||||
|
await sor.U('compute_node', {'id': nid},
|
||||||
|
{'status': 'available', 'updated_at': now})
|
||||||
|
# 清除分配记录
|
||||||
|
await sor.sqlExe(
|
||||||
|
"UPDATE cluster_node SET status='removed' WHERE node_id=${nid}$",
|
||||||
|
{'nid': nid})
|
||||||
|
# 回收资源计数
|
||||||
|
node_row = await sor.R('compute_node', {'id': nid})
|
||||||
|
if node_row:
|
||||||
|
n = node_row[0]
|
||||||
|
await sor.sqlExe(
|
||||||
|
"""UPDATE compute_pool
|
||||||
|
SET allocated_cpu = GREATEST(allocated_cpu - ${cpu}$, 0),
|
||||||
|
allocated_memory_gb = GREATEST(allocated_memory_gb - ${mem}$, 0),
|
||||||
|
allocated_gpu = GREATEST(allocated_gpu - ${gpu}$, 0),
|
||||||
|
updated_at = ${now}$
|
||||||
|
WHERE id = ${pool_id}$""",
|
||||||
|
{'cpu': n.cpu_cores, 'mem': n.memory_gb, 'gpu': n.gpu_count,
|
||||||
|
'pool_id': pool_id or n.pool_id, 'now': now})
|
||||||
|
|
||||||
|
return {'status': 'ok', 'released': len(node_ids)}
|
||||||
|
|
||||||
|
|
||||||
|
async def pool_stats(request, params_kw):
|
||||||
|
"""获取池资源统计"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
if pool_id:
|
||||||
|
pools = await sor.R('compute_pool', {'id': pool_id})
|
||||||
|
else:
|
||||||
|
pools = await sor.R('compute_pool', {})
|
||||||
|
if not pools:
|
||||||
|
return {'status': 'ok', 'data': []}
|
||||||
|
result = []
|
||||||
|
for p in pools:
|
||||||
|
nodes = await sor.R('compute_node', {'pool_id': p.id})
|
||||||
|
result.append({
|
||||||
|
'id': p.id, 'name': p.name, 'type': p.pool_type,
|
||||||
|
'total_cpu': p.total_cpu, 'allocated_cpu': p.allocated_cpu,
|
||||||
|
'total_memory_gb': p.total_memory_gb, 'allocated_memory_gb': p.allocated_memory_gb,
|
||||||
|
'total_gpu': p.total_gpu, 'allocated_gpu': p.allocated_gpu,
|
||||||
|
'node_count': len(nodes),
|
||||||
|
'available_nodes': sum(1 for n in nodes if n.status == 'available'),
|
||||||
|
'status': p.status
|
||||||
|
})
|
||||||
|
return {'status': 'ok', 'data': result}
|
||||||
149
build/lib/build/lib/pcpool/__init__.py
Normal file
149
build/lib/build/lib/pcpool/__init__.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
"""
|
||||||
|
pcpool — 算力中心算力池管理
|
||||||
|
- 算力节点池 CRUD
|
||||||
|
- 节点注册/注销/心跳
|
||||||
|
- 算力单元分配和回收
|
||||||
|
- 池资源统计
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
MODULE_NAME = 'pccs'
|
||||||
|
|
||||||
|
|
||||||
|
async def node_heartbeat(request, params_kw):
|
||||||
|
"""节点心跳上报,更新 last_heartbeat"""
|
||||||
|
env = request._run_ns
|
||||||
|
node_id = params_kw.get('id') or params_kw.get('node_id', '')
|
||||||
|
if not node_id:
|
||||||
|
return {'status': 'error', 'message': 'Missing node id'}
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
now = datetime.datetime.now().isoformat()
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
await sor.U('compute_node', {'id': node_id},
|
||||||
|
{'last_heartbeat': now, 'updated_at': now})
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
|
||||||
|
async def allocate_nodes(request, params_kw):
|
||||||
|
"""从算力池中分配节点给集群。
|
||||||
|
params: pool_id, cluster_id, role (control/compute), count, cpu, memory, gpu
|
||||||
|
返回分配的节点 ID 列表。"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
cluster_id = params_kw.get('cluster_id', '')
|
||||||
|
role = params_kw.get('role', 'compute')
|
||||||
|
count = int(params_kw.get('count', 1))
|
||||||
|
req_cpu = int(params_kw.get('cpu', 0))
|
||||||
|
req_mem = int(params_kw.get('memory', 0))
|
||||||
|
req_gpu = int(params_kw.get('gpu', 0))
|
||||||
|
|
||||||
|
if not pool_id:
|
||||||
|
return {'status': 'error', 'message': 'Missing pool_id'}
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
# 查询可用节点
|
||||||
|
sql = """SELECT * FROM compute_node
|
||||||
|
WHERE pool_id = ${pool_id}$ AND status = 'available'
|
||||||
|
ORDER BY cpu_cores DESC, memory_gb DESC
|
||||||
|
LIMIT ${limit}$"""
|
||||||
|
available = await sor.sqlExe(sql, {'pool_id': pool_id, 'limit': count})
|
||||||
|
if len(available) < count:
|
||||||
|
return {'status': 'error',
|
||||||
|
'message': f'可用节点不足: 需要{count}, 可用{len(available)}'}
|
||||||
|
|
||||||
|
allocated = []
|
||||||
|
for node in available[:count]:
|
||||||
|
await sor.U('compute_node', {'id': node.id},
|
||||||
|
{'status': 'allocated', 'updated_at': datetime.datetime.now().isoformat()})
|
||||||
|
# 记录分配关系
|
||||||
|
cn_id = getID()
|
||||||
|
await sor.C('cluster_node', {
|
||||||
|
'id': cn_id, 'cluster_id': cluster_id,
|
||||||
|
'node_id': node.id, 'role': role, 'status': 'joining',
|
||||||
|
'assigned_at': datetime.datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
allocated.append(node.id)
|
||||||
|
|
||||||
|
# 更新池已分配资源
|
||||||
|
if allocated:
|
||||||
|
await sor.sqlExe(
|
||||||
|
"""UPDATE compute_pool
|
||||||
|
SET allocated_cpu = allocated_cpu + ${cpu}$,
|
||||||
|
allocated_memory_gb = allocated_memory_gb + ${mem}$,
|
||||||
|
allocated_gpu = allocated_gpu + ${gpu}$,
|
||||||
|
updated_at = ${now}$
|
||||||
|
WHERE id = ${pool_id}$""",
|
||||||
|
{'cpu': req_cpu * len(allocated), 'mem': req_mem * len(allocated),
|
||||||
|
'gpu': req_gpu * len(allocated), 'pool_id': pool_id,
|
||||||
|
'now': datetime.datetime.now().isoformat()}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {'status': 'ok', 'allocated_nodes': allocated}
|
||||||
|
|
||||||
|
|
||||||
|
async def release_nodes(request, params_kw):
|
||||||
|
"""回收节点回算力池。
|
||||||
|
params: node_ids (逗号分隔), pool_id"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
node_ids_str = params_kw.get('node_ids', '')
|
||||||
|
if not node_ids_str:
|
||||||
|
return {'status': 'error', 'message': 'Missing node_ids'}
|
||||||
|
node_ids = [n.strip() for n in node_ids_str.split(',') if n.strip()]
|
||||||
|
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
now = datetime.datetime.now().isoformat()
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
for nid in node_ids:
|
||||||
|
await sor.U('compute_node', {'id': nid},
|
||||||
|
{'status': 'available', 'updated_at': now})
|
||||||
|
# 清除分配记录
|
||||||
|
await sor.sqlExe(
|
||||||
|
"UPDATE cluster_node SET status='removed' WHERE node_id=${nid}$",
|
||||||
|
{'nid': nid})
|
||||||
|
# 回收资源计数
|
||||||
|
node_row = await sor.R('compute_node', {'id': nid})
|
||||||
|
if node_row:
|
||||||
|
n = node_row[0]
|
||||||
|
await sor.sqlExe(
|
||||||
|
"""UPDATE compute_pool
|
||||||
|
SET allocated_cpu = GREATEST(allocated_cpu - ${cpu}$, 0),
|
||||||
|
allocated_memory_gb = GREATEST(allocated_memory_gb - ${mem}$, 0),
|
||||||
|
allocated_gpu = GREATEST(allocated_gpu - ${gpu}$, 0),
|
||||||
|
updated_at = ${now}$
|
||||||
|
WHERE id = ${pool_id}$""",
|
||||||
|
{'cpu': n.cpu_cores, 'mem': n.memory_gb, 'gpu': n.gpu_count,
|
||||||
|
'pool_id': pool_id or n.pool_id, 'now': now})
|
||||||
|
|
||||||
|
return {'status': 'ok', 'released': len(node_ids)}
|
||||||
|
|
||||||
|
|
||||||
|
async def pool_stats(request, params_kw):
|
||||||
|
"""获取池资源统计"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
if pool_id:
|
||||||
|
pools = await sor.R('compute_pool', {'id': pool_id})
|
||||||
|
else:
|
||||||
|
pools = await sor.R('compute_pool', {})
|
||||||
|
if not pools:
|
||||||
|
return {'status': 'ok', 'data': []}
|
||||||
|
result = []
|
||||||
|
for p in pools:
|
||||||
|
nodes = await sor.R('compute_node', {'pool_id': p.id})
|
||||||
|
result.append({
|
||||||
|
'id': p.id, 'name': p.name, 'type': p.pool_type,
|
||||||
|
'total_cpu': p.total_cpu, 'allocated_cpu': p.allocated_cpu,
|
||||||
|
'total_memory_gb': p.total_memory_gb, 'allocated_memory_gb': p.allocated_memory_gb,
|
||||||
|
'total_gpu': p.total_gpu, 'allocated_gpu': p.allocated_gpu,
|
||||||
|
'node_count': len(nodes),
|
||||||
|
'available_nodes': sum(1 for n in nodes if n.status == 'available'),
|
||||||
|
'status': p.status
|
||||||
|
})
|
||||||
|
return {'status': 'ok', 'data': result}
|
||||||
68
build/lib/build/lib/scripts/load_path.py
Normal file
68
build/lib/build/lib/scripts/load_path.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""pcpool 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"/pcpool/api/{f}" for f in sorted(os.listdir(API_DIR)) if f.endswith(".dspy")]
|
||||||
|
|
||||||
|
cruds = load_cruds()
|
||||||
|
apis = get_apis()
|
||||||
|
|
||||||
|
PATHS_ANY = [
|
||||||
|
f"/pcpool/menu.ui",
|
||||||
|
]
|
||||||
|
PATHS_LOGINED = [
|
||||||
|
f"/pcpool",
|
||||||
|
f"/pcpool/index.ui",
|
||||||
|
]
|
||||||
|
for d in cruds:
|
||||||
|
PATHS_ANY.append(f"/pcpool/{d['alias']}")
|
||||||
|
PATHS_LOGINED.append(f"/pcpool/{d['alias']}/index.ui")
|
||||||
|
for act in ["get", "add", "update", "delete"]:
|
||||||
|
PATHS_LOGINED.append(f"/pcpool/{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.")
|
||||||
149
build/lib/pcpool/__init__.py
Normal file
149
build/lib/pcpool/__init__.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
"""
|
||||||
|
pcpool — 算力中心算力池管理
|
||||||
|
- 算力节点池 CRUD
|
||||||
|
- 节点注册/注销/心跳
|
||||||
|
- 算力单元分配和回收
|
||||||
|
- 池资源统计
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
MODULE_NAME = 'pccs'
|
||||||
|
|
||||||
|
|
||||||
|
async def node_heartbeat(request, params_kw):
|
||||||
|
"""节点心跳上报,更新 last_heartbeat"""
|
||||||
|
env = request._run_ns
|
||||||
|
node_id = params_kw.get('id') or params_kw.get('node_id', '')
|
||||||
|
if not node_id:
|
||||||
|
return {'status': 'error', 'message': 'Missing node id'}
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
now = datetime.datetime.now().isoformat()
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
await sor.U('compute_node', {'id': node_id},
|
||||||
|
{'last_heartbeat': now, 'updated_at': now})
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
|
||||||
|
async def allocate_nodes(request, params_kw):
|
||||||
|
"""从算力池中分配节点给集群。
|
||||||
|
params: pool_id, cluster_id, role (control/compute), count, cpu, memory, gpu
|
||||||
|
返回分配的节点 ID 列表。"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
cluster_id = params_kw.get('cluster_id', '')
|
||||||
|
role = params_kw.get('role', 'compute')
|
||||||
|
count = int(params_kw.get('count', 1))
|
||||||
|
req_cpu = int(params_kw.get('cpu', 0))
|
||||||
|
req_mem = int(params_kw.get('memory', 0))
|
||||||
|
req_gpu = int(params_kw.get('gpu', 0))
|
||||||
|
|
||||||
|
if not pool_id:
|
||||||
|
return {'status': 'error', 'message': 'Missing pool_id'}
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
# 查询可用节点
|
||||||
|
sql = """SELECT * FROM compute_node
|
||||||
|
WHERE pool_id = ${pool_id}$ AND status = 'available'
|
||||||
|
ORDER BY cpu_cores DESC, memory_gb DESC
|
||||||
|
LIMIT ${limit}$"""
|
||||||
|
available = await sor.sqlExe(sql, {'pool_id': pool_id, 'limit': count})
|
||||||
|
if len(available) < count:
|
||||||
|
return {'status': 'error',
|
||||||
|
'message': f'可用节点不足: 需要{count}, 可用{len(available)}'}
|
||||||
|
|
||||||
|
allocated = []
|
||||||
|
for node in available[:count]:
|
||||||
|
await sor.U('compute_node', {'id': node.id},
|
||||||
|
{'status': 'allocated', 'updated_at': datetime.datetime.now().isoformat()})
|
||||||
|
# 记录分配关系
|
||||||
|
cn_id = getID()
|
||||||
|
await sor.C('cluster_node', {
|
||||||
|
'id': cn_id, 'cluster_id': cluster_id,
|
||||||
|
'node_id': node.id, 'role': role, 'status': 'joining',
|
||||||
|
'assigned_at': datetime.datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
allocated.append(node.id)
|
||||||
|
|
||||||
|
# 更新池已分配资源
|
||||||
|
if allocated:
|
||||||
|
await sor.sqlExe(
|
||||||
|
"""UPDATE compute_pool
|
||||||
|
SET allocated_cpu = allocated_cpu + ${cpu}$,
|
||||||
|
allocated_memory_gb = allocated_memory_gb + ${mem}$,
|
||||||
|
allocated_gpu = allocated_gpu + ${gpu}$,
|
||||||
|
updated_at = ${now}$
|
||||||
|
WHERE id = ${pool_id}$""",
|
||||||
|
{'cpu': req_cpu * len(allocated), 'mem': req_mem * len(allocated),
|
||||||
|
'gpu': req_gpu * len(allocated), 'pool_id': pool_id,
|
||||||
|
'now': datetime.datetime.now().isoformat()}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {'status': 'ok', 'allocated_nodes': allocated}
|
||||||
|
|
||||||
|
|
||||||
|
async def release_nodes(request, params_kw):
|
||||||
|
"""回收节点回算力池。
|
||||||
|
params: node_ids (逗号分隔), pool_id"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
node_ids_str = params_kw.get('node_ids', '')
|
||||||
|
if not node_ids_str:
|
||||||
|
return {'status': 'error', 'message': 'Missing node_ids'}
|
||||||
|
node_ids = [n.strip() for n in node_ids_str.split(',') if n.strip()]
|
||||||
|
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
now = datetime.datetime.now().isoformat()
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
for nid in node_ids:
|
||||||
|
await sor.U('compute_node', {'id': nid},
|
||||||
|
{'status': 'available', 'updated_at': now})
|
||||||
|
# 清除分配记录
|
||||||
|
await sor.sqlExe(
|
||||||
|
"UPDATE cluster_node SET status='removed' WHERE node_id=${nid}$",
|
||||||
|
{'nid': nid})
|
||||||
|
# 回收资源计数
|
||||||
|
node_row = await sor.R('compute_node', {'id': nid})
|
||||||
|
if node_row:
|
||||||
|
n = node_row[0]
|
||||||
|
await sor.sqlExe(
|
||||||
|
"""UPDATE compute_pool
|
||||||
|
SET allocated_cpu = GREATEST(allocated_cpu - ${cpu}$, 0),
|
||||||
|
allocated_memory_gb = GREATEST(allocated_memory_gb - ${mem}$, 0),
|
||||||
|
allocated_gpu = GREATEST(allocated_gpu - ${gpu}$, 0),
|
||||||
|
updated_at = ${now}$
|
||||||
|
WHERE id = ${pool_id}$""",
|
||||||
|
{'cpu': n.cpu_cores, 'mem': n.memory_gb, 'gpu': n.gpu_count,
|
||||||
|
'pool_id': pool_id or n.pool_id, 'now': now})
|
||||||
|
|
||||||
|
return {'status': 'ok', 'released': len(node_ids)}
|
||||||
|
|
||||||
|
|
||||||
|
async def pool_stats(request, params_kw):
|
||||||
|
"""获取池资源统计"""
|
||||||
|
env = request._run_ns
|
||||||
|
dbname = env.get_module_dbname(MODULE_NAME)
|
||||||
|
pool_id = params_kw.get('pool_id', '')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
if pool_id:
|
||||||
|
pools = await sor.R('compute_pool', {'id': pool_id})
|
||||||
|
else:
|
||||||
|
pools = await sor.R('compute_pool', {})
|
||||||
|
if not pools:
|
||||||
|
return {'status': 'ok', 'data': []}
|
||||||
|
result = []
|
||||||
|
for p in pools:
|
||||||
|
nodes = await sor.R('compute_node', {'pool_id': p.id})
|
||||||
|
result.append({
|
||||||
|
'id': p.id, 'name': p.name, 'type': p.pool_type,
|
||||||
|
'total_cpu': p.total_cpu, 'allocated_cpu': p.allocated_cpu,
|
||||||
|
'total_memory_gb': p.total_memory_gb, 'allocated_memory_gb': p.allocated_memory_gb,
|
||||||
|
'total_gpu': p.total_gpu, 'allocated_gpu': p.allocated_gpu,
|
||||||
|
'node_count': len(nodes),
|
||||||
|
'available_nodes': sum(1 for n in nodes if n.status == 'available'),
|
||||||
|
'status': p.status
|
||||||
|
})
|
||||||
|
return {'status': 'ok', 'data': result}
|
||||||
68
build/lib/scripts/load_path.py
Normal file
68
build/lib/scripts/load_path.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""pcpool 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"/pcpool/api/{f}" for f in sorted(os.listdir(API_DIR)) if f.endswith(".dspy")]
|
||||||
|
|
||||||
|
cruds = load_cruds()
|
||||||
|
apis = get_apis()
|
||||||
|
|
||||||
|
PATHS_ANY = [
|
||||||
|
f"/pcpool/menu.ui",
|
||||||
|
]
|
||||||
|
PATHS_LOGINED = [
|
||||||
|
f"/pcpool",
|
||||||
|
f"/pcpool/index.ui",
|
||||||
|
]
|
||||||
|
for d in cruds:
|
||||||
|
PATHS_ANY.append(f"/pcpool/{d['alias']}")
|
||||||
|
PATHS_LOGINED.append(f"/pcpool/{d['alias']}/index.ui")
|
||||||
|
for act in ["get", "add", "update", "delete"]:
|
||||||
|
PATHS_LOGINED.append(f"/pcpool/{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.")
|
||||||
31
i18n/en/msg.txt
Normal file
31
i18n/en/msg.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
CPU核数: CPU Cores
|
||||||
|
GPU型号: GPU Model
|
||||||
|
GPU数:
|
||||||
|
GPU数量: GPU Count
|
||||||
|
IP地址: IP Address
|
||||||
|
SSH用户: SSH User
|
||||||
|
SSH端口: SSH Port
|
||||||
|
内存GB: Memory GB
|
||||||
|
创建时间: Created At
|
||||||
|
商户机构id: Reseller ID
|
||||||
|
已分配CPU: Allocated CPU
|
||||||
|
已分配GPU: Allocated GPUs
|
||||||
|
已分配内存GB: Allocated Memory GB
|
||||||
|
总CPU核数: Total CPU Cores
|
||||||
|
总GPU数: Total GPUs
|
||||||
|
总内存GB: Total Memory GB
|
||||||
|
所属算力池: Compute Pool
|
||||||
|
描述: Description
|
||||||
|
更新时间: Updated At
|
||||||
|
最后心跳: Last Heartbeat
|
||||||
|
池名称: Pool Name
|
||||||
|
状态: Status
|
||||||
|
状态(1=启用 0=停用): Status (1=Enabled 0=Disabled)
|
||||||
|
状态(available/allocated/maintenance/offline):
|
||||||
|
算力池: Compute Pool
|
||||||
|
算力节点:
|
||||||
|
节点名称:
|
||||||
|
节点类型:
|
||||||
|
节点类型(control/compute/storage):
|
||||||
|
集群类型:
|
||||||
|
集群类型(k8s/slurm/ray):
|
||||||
31
i18n/zh/msg.txt
Normal file
31
i18n/zh/msg.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
CPU核数: CPU核数
|
||||||
|
GPU型号: GPU型号
|
||||||
|
GPU数: GPU数
|
||||||
|
GPU数量: GPU数量
|
||||||
|
IP地址: IP地址
|
||||||
|
SSH用户: SSH用户
|
||||||
|
SSH端口: SSH端口
|
||||||
|
内存GB: 内存GB
|
||||||
|
创建时间: 创建时间
|
||||||
|
商户机构id: 商户机构id
|
||||||
|
已分配CPU: 已分配CPU
|
||||||
|
已分配GPU: 已分配GPU
|
||||||
|
已分配内存GB: 已分配内存GB
|
||||||
|
总CPU核数: 总CPU核数
|
||||||
|
总GPU数: 总GPU数
|
||||||
|
总内存GB: 总内存GB
|
||||||
|
所属算力池: 所属算力池
|
||||||
|
描述: 描述
|
||||||
|
更新时间: 更新时间
|
||||||
|
最后心跳: 最后心跳
|
||||||
|
池名称: 池名称
|
||||||
|
状态: 状态
|
||||||
|
状态(1=启用 0=停用): 状态(1=启用 0=停用)
|
||||||
|
状态(available/allocated/maintenance/offline): 状态(available/allocated/maintenance/offline)
|
||||||
|
算力池: 算力池
|
||||||
|
算力节点: 算力节点
|
||||||
|
节点名称: 节点名称
|
||||||
|
节点类型: 节点类型
|
||||||
|
节点类型(control/compute/storage): 节点类型(control/compute/storage)
|
||||||
|
集群类型: 集群类型
|
||||||
|
集群类型(k8s/slurm/ray): 集群类型(k8s/slurm/ray)
|
||||||
@ -47,13 +47,12 @@
|
|||||||
"created_at",
|
"created_at",
|
||||||
"updated_at"
|
"updated_at"
|
||||||
],
|
],
|
||||||
"toolbar": {
|
|
||||||
"tools": []
|
|
||||||
},
|
|
||||||
"binds": [],
|
"binds": [],
|
||||||
"new_data_url": "{{entire_url('/pcpool/api/compute_node_create.dspy')}}",
|
"logined_userorgid": "resellerid",
|
||||||
"update_data_url": "{{entire_url('/pcpool/api/compute_node_update.dspy')}}",
|
"editable": {
|
||||||
"delete_data_url": "{{entire_url('/pcpool/api/compute_node_delete.dspy')}}",
|
"new_data_url": "{{entire_url('/pcpool/api/compute_node_create.dspy')}}",
|
||||||
"logined_userorgid": "resellerid"
|
"update_data_url": "{{entire_url('/pcpool/api/compute_node_update.dspy')}}",
|
||||||
|
"delete_data_url": "{{entire_url('/pcpool/api/compute_node_delete.dspy')}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12,30 +12,6 @@
|
|||||||
"title": "集群类型",
|
"title": "集群类型",
|
||||||
"width": 100
|
"width": 100
|
||||||
},
|
},
|
||||||
"total_cpu": {
|
|
||||||
"title": "总CPU核数",
|
|
||||||
"width": 100
|
|
||||||
},
|
|
||||||
"total_memory_gb": {
|
|
||||||
"title": "总内存GB",
|
|
||||||
"width": 100
|
|
||||||
},
|
|
||||||
"total_gpu": {
|
|
||||||
"title": "总GPU数",
|
|
||||||
"width": 80
|
|
||||||
},
|
|
||||||
"allocated_cpu": {
|
|
||||||
"title": "已分配CPU",
|
|
||||||
"width": 100
|
|
||||||
},
|
|
||||||
"allocated_memory_gb": {
|
|
||||||
"title": "已分配内存GB",
|
|
||||||
"width": 120
|
|
||||||
},
|
|
||||||
"allocated_gpu": {
|
|
||||||
"title": "已分配GPU",
|
|
||||||
"width": 100
|
|
||||||
},
|
|
||||||
"status": {
|
"status": {
|
||||||
"title": "状态",
|
"title": "状态",
|
||||||
"width": 80
|
"width": 80
|
||||||
@ -44,16 +20,27 @@
|
|||||||
"editexclouded": [
|
"editexclouded": [
|
||||||
"id",
|
"id",
|
||||||
"resellerid",
|
"resellerid",
|
||||||
|
"description",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at"
|
"updated_at"
|
||||||
],
|
],
|
||||||
"toolbar": {
|
|
||||||
"tools": []
|
|
||||||
},
|
|
||||||
"binds": [],
|
"binds": [],
|
||||||
"new_data_url": "{{entire_url('/pcpool/api/compute_pool_create.dspy')}}",
|
"logined_userorgid": "resellerid",
|
||||||
"update_data_url": "{{entire_url('/pcpool/api/compute_pool_update.dspy')}}",
|
"editable": {
|
||||||
"delete_data_url": "{{entire_url('/pcpool/api/compute_pool_delete.dspy')}}",
|
"new_data_url": "{{entire_url('/pcpool/api/compute_pool_create.dspy')}}",
|
||||||
"logined_userorgid": "resellerid"
|
"update_data_url": "{{entire_url('/pcpool/api/compute_pool_update.dspy')}}",
|
||||||
|
"delete_data_url": "{{entire_url('/pcpool/api/compute_pool_delete.dspy')}}"
|
||||||
|
},
|
||||||
|
"toolbar": {
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "view_nodes",
|
||||||
|
"label": "查看节点",
|
||||||
|
"icon": "",
|
||||||
|
"type": "link",
|
||||||
|
"url": "/pcpool/compute_node_list/index.ui?pool_id={{id}}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,44 +41,7 @@
|
|||||||
{
|
{
|
||||||
"name": "description",
|
"name": "description",
|
||||||
"title": "描述",
|
"title": "描述",
|
||||||
"type": "str",
|
"type": "text"
|
||||||
"length": 512
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "total_cpu",
|
|
||||||
"title": "总CPU核数",
|
|
||||||
"type": "int",
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "total_memory_gb",
|
|
||||||
"title": "总内存GB",
|
|
||||||
"type": "int",
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "total_gpu",
|
|
||||||
"title": "总GPU数",
|
|
||||||
"type": "int",
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "allocated_cpu",
|
|
||||||
"title": "已分配CPU",
|
|
||||||
"type": "int",
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "allocated_memory_gb",
|
|
||||||
"title": "已分配内存GB",
|
|
||||||
"type": "int",
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "allocated_gpu",
|
|
||||||
"title": "已分配GPU",
|
|
||||||
"type": "int",
|
|
||||||
"default": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "status",
|
"name": "status",
|
||||||
|
|||||||
9
pcpool.egg-info/PKG-INFO
Normal file
9
pcpool.egg-info/PKG-INFO
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: pcpool
|
||||||
|
Version: 0.1.0
|
||||||
|
Summary: 算力中心算力池管理:节点池CRUD、节点注册/注销、算力单元分配回收
|
||||||
|
Requires-Python: >=3.10
|
||||||
|
Requires-Dist: apppublic
|
||||||
|
Requires-Dist: sqlor
|
||||||
|
Requires-Dist: ahserver
|
||||||
|
Requires-Dist: appbase
|
||||||
9
pcpool.egg-info/SOURCES.txt
Normal file
9
pcpool.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
README.md
|
||||||
|
pyproject.toml
|
||||||
|
pcpool/__init__.py
|
||||||
|
pcpool.egg-info/PKG-INFO
|
||||||
|
pcpool.egg-info/SOURCES.txt
|
||||||
|
pcpool.egg-info/dependency_links.txt
|
||||||
|
pcpool.egg-info/requires.txt
|
||||||
|
pcpool.egg-info/top_level.txt
|
||||||
|
scripts/load_path.py
|
||||||
1
pcpool.egg-info/dependency_links.txt
Normal file
1
pcpool.egg-info/dependency_links.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
4
pcpool.egg-info/requires.txt
Normal file
4
pcpool.egg-info/requires.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apppublic
|
||||||
|
sqlor
|
||||||
|
ahserver
|
||||||
|
appbase
|
||||||
7
pcpool.egg-info/top_level.txt
Normal file
7
pcpool.egg-info/top_level.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
build
|
||||||
|
i18n
|
||||||
|
json
|
||||||
|
models
|
||||||
|
pcpool
|
||||||
|
scripts
|
||||||
|
wwwroot
|
||||||
16
pyproject.toml
Normal file
16
pyproject.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "pcpool"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "算力中心算力池管理:节点池CRUD、节点注册/注销、算力单元分配回收"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
dependencies = ["apppublic", "sqlor", "ahserver", "appbase"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-dir]
|
||||||
|
"pcpool" = "pcpool"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
@ -1,3 +1,21 @@
|
|||||||
# compute_pool create
|
ns = params_kw.copy()
|
||||||
result = {'status': 'ok', 'message': 'compute_pool created'}
|
for k,v in list(ns.items()):
|
||||||
return result
|
if v == 'NaN' or v == 'null':
|
||||||
|
ns[k] = None
|
||||||
|
if k.endswith('_text'):
|
||||||
|
ns.pop(k, None)
|
||||||
|
id = params_kw.get('id','')
|
||||||
|
if not id or len(str(id)) > 32:
|
||||||
|
id = uuid()
|
||||||
|
ns['id'] = id
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {'widgettype':'Error','options':{'title':'Authorization Error','timeout':3,'cwidth':16,'cheight':9,'message':'Please login'}}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
ns['created_at'] = datetime.datetime.now().isoformat()
|
||||||
|
ns['updated_at'] = ns['created_at']
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.C('compute_pool', ns)
|
||||||
|
return {'widgettype':'Message','options':{'cwidth':16,'cheight':9,'title':'Success','timeout':3,'message':'ok'}}
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
# compute_pool delete
|
ns = params_kw.copy()
|
||||||
result = {'status': 'ok', 'message': 'compute_pool deleted'}
|
if not ns.get('id'):
|
||||||
return result
|
return {'widgettype':'Error','options':{'title':'Error','message':'missing id','cwidth':16,'cheight':9,'timeout':3}}
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.D('compute_pool', {'id': ns['id']})
|
||||||
|
return {'widgettype':'Message','options':{'cwidth':16,'cheight':9,'title':'Success','timeout':3,'message':'ok'}}
|
||||||
|
|||||||
@ -1,3 +1,14 @@
|
|||||||
# compute_pool update
|
ns = params_kw.copy()
|
||||||
result = {'status': 'ok', 'message': 'compute_pool updated'}
|
for k,v in list(ns.items()):
|
||||||
return result
|
if v == 'NaN' or v == 'null':
|
||||||
|
ns[k] = None
|
||||||
|
if k.endswith('_text'):
|
||||||
|
ns.pop(k, None)
|
||||||
|
if not ns.get('id'):
|
||||||
|
return {'widgettype':'Error','options':{'title':'Error','message':'missing id','cwidth':16,'cheight':9,'timeout':3}}
|
||||||
|
ns['updated_at'] = datetime.datetime.now().isoformat()
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.U('compute_pool', ns)
|
||||||
|
return {'widgettype':'Message','options':{'cwidth':16,'cheight':9,'title':'Success','timeout':3,'message':'ok'}}
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
node_id = params_kw.get('id', '')
|
node_id = params_kw.get('id', '')
|
||||||
if not node_id:
|
if not node_id:
|
||||||
return {'status': 'error', 'message': 'Missing node id'}
|
return {'status': 'error', 'message': 'Missing node id'}
|
||||||
import datetime
|
|
||||||
async with DBPools().sqlorContext('pccs') as sor:
|
async with DBPools().sqlorContext('pccs') as sor:
|
||||||
await sor.U('compute_node', {'id': node_id}, {'last_heartbeat': datetime.datetime.now().isoformat(), 'updated_at': datetime.datetime.now().isoformat()})
|
await sor.U('compute_node', {'id': node_id, 'last_heartbeat': datetime.datetime.now().isoformat(), 'updated_at': datetime.datetime.now().isoformat()})
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|||||||
@ -1,4 +1,50 @@
|
|||||||
# 算力池资源统计
|
env = request._run_ns
|
||||||
# params: pool_id (可选,不传则返回全部池)
|
dbname = get_module_dbname('pcpool')
|
||||||
result = await pool_stats(request, params_kw)
|
try:
|
||||||
return result
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
pools = await sor.R('compute_pool', {})
|
||||||
|
total_pools = len(pools or [])
|
||||||
|
active = sum(1 for p in (pools or []) if str(getattr(p,'status','')) == '1')
|
||||||
|
total_cpu = 0; total_gpu = 0; total_mem = 0
|
||||||
|
alloc_cpu = 0; alloc_gpu = 0; alloc_mem = 0
|
||||||
|
for p in (pools or []):
|
||||||
|
nodes = await sor.R('compute_node', {'pool_id': p.id})
|
||||||
|
for n in (nodes or []):
|
||||||
|
cpu = int(getattr(n,'cpu_cores',0) or 0)
|
||||||
|
gpu = int(getattr(n,'gpu_count',0) or 0)
|
||||||
|
mem = int(getattr(n,'memory_gb',0) or 0)
|
||||||
|
total_cpu += cpu; total_gpu += gpu; total_mem += mem
|
||||||
|
if getattr(n,'status','') == 'allocated':
|
||||||
|
alloc_cpu += cpu; alloc_gpu += gpu; alloc_mem += mem
|
||||||
|
return {
|
||||||
|
'widgettype': 'HBox',
|
||||||
|
'options': {'gap': '16px'},
|
||||||
|
'subwidgets': [
|
||||||
|
{'widgettype': 'VBox', 'options': {'bgcolor': '#eff6ff', 'padding': '16px', 'width': '25%', 'border': '1px solid #dbeafe', 'borderRadius': '8px'},
|
||||||
|
'subwidgets': [
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': '算力池', 'cfontsize': 0.7, 'color': '#64748b'}},
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': str(total_pools) + ' 个', 'cfontsize': 2, 'fontWeight': 'bold', 'color': '#2563eb'}},
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': str(active) + ' 运行中', 'cfontsize': 0.7, 'color': '#94a3b8'}}
|
||||||
|
]},
|
||||||
|
{'widgettype': 'VBox', 'options': {'bgcolor': '#f5f3ff', 'padding': '16px', 'width': '25%', 'border': '1px solid #ede9fe', 'borderRadius': '8px'},
|
||||||
|
'subwidgets': [
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': 'CPU', 'cfontsize': 0.7, 'color': '#64748b'}},
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': str(total_cpu) + ' 核', 'cfontsize': 2, 'fontWeight': 'bold', 'color': '#7c3aed'}},
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': '已分配 ' + str(alloc_cpu) + ' 核', 'cfontsize': 0.7, 'color': '#94a3b8'}}
|
||||||
|
]},
|
||||||
|
{'widgettype': 'VBox', 'options': {'bgcolor': '#ecfdf5', 'padding': '16px', 'width': '25%', 'border': '1px solid #d1fae5', 'borderRadius': '8px'},
|
||||||
|
'subwidgets': [
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': 'GPU', 'cfontsize': 0.7, 'color': '#64748b'}},
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': str(total_gpu) + ' 个', 'cfontsize': 2, 'fontWeight': 'bold', 'color': '#059669'}},
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': '已分配 ' + str(alloc_gpu) + ' 个', 'cfontsize': 0.7, 'color': '#94a3b8'}}
|
||||||
|
]},
|
||||||
|
{'widgettype': 'VBox', 'options': {'bgcolor': '#fff7ed', 'padding': '16px', 'width': '25%', 'border': '1px solid #ffedd5', 'borderRadius': '8px'},
|
||||||
|
'subwidgets': [
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': '内存', 'cfontsize': 0.7, 'color': '#64748b'}},
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': str(total_mem) + ' GB', 'cfontsize': 2, 'fontWeight': 'bold', 'color': '#ea580c'}},
|
||||||
|
{'widgettype': 'Text', 'options': {'otext': '已分配 ' + str(alloc_mem) + ' GB', 'cfontsize': 0.7, 'color': '#94a3b8'}}
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
except:
|
||||||
|
return {'widgettype': 'Text', 'options': {'otext': '加载失败', 'color': '#dc2626'}}
|
||||||
|
|||||||
57
wwwroot/compute_node_list/add_compute_node.dspy
Normal file
57
wwwroot/compute_node_list/add_compute_node.dspy
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
for k,v in ns.items():
|
||||||
|
if v == 'NaN' or v == 'null':
|
||||||
|
ns[k] = None
|
||||||
|
id = params_kw.id
|
||||||
|
if not id or len(id) > 32:
|
||||||
|
id = uuid()
|
||||||
|
ns['id'] = id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Authorization Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"Please login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
|
||||||
|
for k in list(ns.keys()):
|
||||||
|
if k.endswith('_at') or k.endswith('_time') or k == 'last_heartbeat':
|
||||||
|
v = ns.get(k, '')
|
||||||
|
if v == '' or v is None or v == 'None':
|
||||||
|
ns[k] = None
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.C('compute_node', ns.copy())
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"user_data":ns,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"title":"Add Success",
|
||||||
|
"timeout":3,
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Add Error",
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"timeout":3,
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
47
wwwroot/compute_node_list/delete_compute_node.dspy
Normal file
47
wwwroot/compute_node_list/delete_compute_node.dspy
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
ns = {
|
||||||
|
'id':params_kw['id'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Authorization Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"Please login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.D('compute_node', ns)
|
||||||
|
debug('delete success');
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"title":"Delete Success",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Delete failed');
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Delete Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
158
wwwroot/compute_node_list/get_compute_node.dspy
Normal file
158
wwwroot/compute_node_list/get_compute_node.dspy
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
|
||||||
|
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Authorization Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"Please login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
ns['userorgid'] = userorgid
|
||||||
|
|
||||||
|
debug(f'get_compute_node.dspy:{ns=}')
|
||||||
|
if not ns.get('page'):
|
||||||
|
ns['page'] = 1
|
||||||
|
if not ns.get('sort'):
|
||||||
|
|
||||||
|
ns['sort'] = 'id'
|
||||||
|
|
||||||
|
|
||||||
|
sql = '''select a.*, b.pool_id_text, c.node_type_text, d.status_text
|
||||||
|
from (select * from compute_node where 1=1 [[filterstr]]) a left join (select id as pool_id,
|
||||||
|
name as pool_id_text from compute_pool where 1 = 1) b on a.pool_id = b.pool_id left join (select k as node_type,
|
||||||
|
v as node_type_text from appcodes_kv where parentid='node_type') c on a.node_type = c.node_type left join (select k as status,
|
||||||
|
v as status_text from appcodes_kv where parentid='node_status') d on a.status = d.status'''
|
||||||
|
|
||||||
|
filterjson = params_kw.get('data_filter')
|
||||||
|
if not filterjson:
|
||||||
|
fields = [ f['name'] for f in [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"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": "ip_address",
|
||||||
|
"title": "IP地址",
|
||||||
|
"type": "str",
|
||||||
|
"length": 64,
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ssh_port",
|
||||||
|
"title": "SSH端口",
|
||||||
|
"type": "int",
|
||||||
|
"default": 22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ssh_user",
|
||||||
|
"title": "SSH用户",
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cpu_cores",
|
||||||
|
"title": "CPU核数",
|
||||||
|
"type": "int",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "memory_gb",
|
||||||
|
"title": "内存GB",
|
||||||
|
"type": "int",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gpu_count",
|
||||||
|
"title": "GPU数量",
|
||||||
|
"type": "int",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gpu_type",
|
||||||
|
"title": "GPU型号",
|
||||||
|
"type": "str",
|
||||||
|
"length": 64
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "node_type",
|
||||||
|
"title": "节点类型(control/compute/storage)",
|
||||||
|
"type": "char",
|
||||||
|
"length": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"title": "状态(available/allocated/maintenance/offline)",
|
||||||
|
"type": "char",
|
||||||
|
"length": 16,
|
||||||
|
"default": "available"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last_heartbeat",
|
||||||
|
"title": "最后心跳",
|
||||||
|
"type": "timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"title": "创建时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"title": "更新时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": "no"
|
||||||
|
}
|
||||||
|
] ]
|
||||||
|
filterjson = default_filterjson(fields, ns)
|
||||||
|
filterdic = ns.copy()
|
||||||
|
filterdic['filterstr'] = ''
|
||||||
|
filterdic['userorgid'] = '${userorgid}$'
|
||||||
|
filterdic['userid'] = '${userid}$'
|
||||||
|
if filterjson:
|
||||||
|
dbf = DBFilter(filterjson)
|
||||||
|
conds = dbf.gen(ns)
|
||||||
|
if conds:
|
||||||
|
ns.update(dbf.consts)
|
||||||
|
conds = f' and {conds}'
|
||||||
|
filterdic['filterstr'] = conds
|
||||||
|
ac = ArgsConvert('[[', ']]')
|
||||||
|
vars = ac.findAllVariables(sql)
|
||||||
|
NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' }
|
||||||
|
filterdic.update(NameSpace)
|
||||||
|
sql = ac.convert(sql, filterdic)
|
||||||
|
|
||||||
|
debug(f'{sql=}')
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.sqlPaging(sql, ns)
|
||||||
|
return r
|
||||||
|
return {
|
||||||
|
"total":0,
|
||||||
|
"rows":[]
|
||||||
|
}
|
||||||
280
wwwroot/compute_node_list/index.ui
Normal file
280
wwwroot/compute_node_list/index.ui
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"id":"compute_node_tbl",
|
||||||
|
"widgettype":"Tabular",
|
||||||
|
"options":{
|
||||||
|
"width":"100%",
|
||||||
|
"height":"100%",
|
||||||
|
|
||||||
|
|
||||||
|
"title":"算力节点",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"css":"card",
|
||||||
|
|
||||||
|
|
||||||
|
"editable":{
|
||||||
|
|
||||||
|
"new_data_url":"{{entire_url('add_compute_node.dspy')}}",
|
||||||
|
|
||||||
|
|
||||||
|
"delete_data_url":"{{entire_url('delete_compute_node.dspy')}}",
|
||||||
|
|
||||||
|
|
||||||
|
"update_data_url":"{{entire_url('update_compute_node.dspy')}}"
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
"data_url":"{{entire_url('./get_compute_node.dspy')}}",
|
||||||
|
|
||||||
|
"data_method":"GET",
|
||||||
|
"data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}},
|
||||||
|
"row_options":{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"browserfields": {
|
||||||
|
"pool_id": {
|
||||||
|
"title": "所属算力池",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"title": "节点名称",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
"ip_address": {
|
||||||
|
"title": "IP地址",
|
||||||
|
"width": 140
|
||||||
|
},
|
||||||
|
"cpu_cores": {
|
||||||
|
"title": "CPU核数",
|
||||||
|
"width": 80
|
||||||
|
},
|
||||||
|
"memory_gb": {
|
||||||
|
"title": "内存GB",
|
||||||
|
"width": 80
|
||||||
|
},
|
||||||
|
"gpu_count": {
|
||||||
|
"title": "GPU数",
|
||||||
|
"width": 70
|
||||||
|
},
|
||||||
|
"gpu_type": {
|
||||||
|
"title": "GPU型号",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
"node_type": {
|
||||||
|
"title": "节点类型",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"title": "状态",
|
||||||
|
"width": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
"editexclouded":[
|
||||||
|
"id",
|
||||||
|
"resellerid",
|
||||||
|
"created_at",
|
||||||
|
"updated_at"
|
||||||
|
],
|
||||||
|
|
||||||
|
"fields":[
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": "no",
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pool_id",
|
||||||
|
"title": "所属算力池",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"label": "所属算力池",
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "pool_id",
|
||||||
|
"textField": "pool_id_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "{{get_module_dbname('pcpool')}}",
|
||||||
|
"table": "compute_pool",
|
||||||
|
"tblvalue": "id",
|
||||||
|
"tbltext": "name",
|
||||||
|
"valueField": "pool_id",
|
||||||
|
"textField": "pool_id_text"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"title": "节点名称",
|
||||||
|
"type": "str",
|
||||||
|
"length": 128,
|
||||||
|
"nullable": "no",
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "节点名称"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ip_address",
|
||||||
|
"title": "IP地址",
|
||||||
|
"type": "str",
|
||||||
|
"length": 64,
|
||||||
|
"nullable": "no",
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "IP地址"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ssh_port",
|
||||||
|
"title": "SSH端口",
|
||||||
|
"type": "int",
|
||||||
|
"default": 22,
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "int",
|
||||||
|
"datatype": "int",
|
||||||
|
"label": "SSH端口"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ssh_user",
|
||||||
|
"title": "SSH用户",
|
||||||
|
"type": "str",
|
||||||
|
"length": 64,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "SSH用户"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cpu_cores",
|
||||||
|
"title": "CPU核数",
|
||||||
|
"type": "int",
|
||||||
|
"default": 0,
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "int",
|
||||||
|
"datatype": "int",
|
||||||
|
"label": "CPU核数"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "memory_gb",
|
||||||
|
"title": "内存GB",
|
||||||
|
"type": "int",
|
||||||
|
"default": 0,
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "int",
|
||||||
|
"datatype": "int",
|
||||||
|
"label": "内存GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gpu_count",
|
||||||
|
"title": "GPU数量",
|
||||||
|
"type": "int",
|
||||||
|
"default": 0,
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "int",
|
||||||
|
"datatype": "int",
|
||||||
|
"label": "GPU数量"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gpu_type",
|
||||||
|
"title": "GPU型号",
|
||||||
|
"type": "str",
|
||||||
|
"length": 64,
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "GPU型号"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "node_type",
|
||||||
|
"title": "节点类型(control/compute/storage)",
|
||||||
|
"type": "char",
|
||||||
|
"length": 16,
|
||||||
|
"label": "节点类型(control/compute/storage)",
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "node_type",
|
||||||
|
"textField": "node_type_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "{{get_module_dbname('pcpool')}}",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"tblvalue": "k",
|
||||||
|
"tbltext": "v",
|
||||||
|
"valueField": "node_type",
|
||||||
|
"textField": "node_type_text",
|
||||||
|
"cond": "parentid='node_type'"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"title": "状态(available/allocated/maintenance/offline)",
|
||||||
|
"type": "char",
|
||||||
|
"length": 16,
|
||||||
|
"default": "available",
|
||||||
|
"label": "状态(available/allocated/maintenance/offline)",
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "status",
|
||||||
|
"textField": "status_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "{{get_module_dbname('pcpool')}}",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"tblvalue": "k",
|
||||||
|
"tbltext": "v",
|
||||||
|
"valueField": "status",
|
||||||
|
"textField": "status_text",
|
||||||
|
"cond": "parentid='node_status'"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "last_heartbeat",
|
||||||
|
"title": "最后心跳",
|
||||||
|
"type": "timestamp",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "timestamp",
|
||||||
|
"label": "最后心跳"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"title": "创建时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": "no",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "timestamp",
|
||||||
|
"label": "创建时间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"title": "更新时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": "no",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "timestamp",
|
||||||
|
"label": "更新时间"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"page_rows":160,
|
||||||
|
"cache_limit":5
|
||||||
|
}
|
||||||
|
|
||||||
|
,"binds":[]
|
||||||
|
|
||||||
|
}
|
||||||
75
wwwroot/compute_node_list/update_compute_node.dspy
Normal file
75
wwwroot/compute_node_list/update_compute_node.dspy
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
for k,v in ns.items():
|
||||||
|
if v == 'NaN' or v == 'null':
|
||||||
|
ns[k] = None
|
||||||
|
|
||||||
|
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Authorization Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"Please login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for k in list(ns.keys()):
|
||||||
|
if k.endswith('_at') or k.endswith('_time') or k == 'last_heartbeat':
|
||||||
|
v = ns.get(k, '')
|
||||||
|
if v == '' or v is None or v == 'None':
|
||||||
|
ns[k] = None
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
|
||||||
|
ns1 = {
|
||||||
|
|
||||||
|
"resellerid": userorgid,
|
||||||
|
|
||||||
|
|
||||||
|
"id": params_kw.id
|
||||||
|
}
|
||||||
|
recs = await sor.R('compute_node', ns1)
|
||||||
|
if len(recs) < 1:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Update Error",
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"timeout":3,
|
||||||
|
"message":"Record no exist or with wrong ownership"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = await sor.U('compute_node', ns)
|
||||||
|
debug('update success');
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"title":"Update Success",
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"timeout":3,
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Update Error",
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"timeout":3,
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
52
wwwroot/compute_pool_list/add_compute_pool.dspy
Normal file
52
wwwroot/compute_pool_list/add_compute_pool.dspy
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
for k,v in ns.items():
|
||||||
|
if v == 'NaN' or v == 'null':
|
||||||
|
ns[k] = None
|
||||||
|
id = params_kw.id
|
||||||
|
if not id or len(id) > 32:
|
||||||
|
id = uuid()
|
||||||
|
ns['id'] = id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Authorization Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"Please login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.C('compute_pool', ns.copy())
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"user_data":ns,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"title":"Add Success",
|
||||||
|
"timeout":3,
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Add Error",
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"timeout":3,
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
47
wwwroot/compute_pool_list/delete_compute_pool.dspy
Normal file
47
wwwroot/compute_pool_list/delete_compute_pool.dspy
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
ns = {
|
||||||
|
'id':params_kw['id'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Authorization Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"Please login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.D('compute_pool', ns)
|
||||||
|
debug('delete success');
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"title":"Delete Success",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Delete failed');
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Delete Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
116
wwwroot/compute_pool_list/get_compute_pool.dspy
Normal file
116
wwwroot/compute_pool_list/get_compute_pool.dspy
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
|
||||||
|
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Authorization Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"Please login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
ns['userorgid'] = userorgid
|
||||||
|
|
||||||
|
debug(f'get_compute_pool.dspy:{ns=}')
|
||||||
|
if not ns.get('page'):
|
||||||
|
ns['page'] = 1
|
||||||
|
if not ns.get('sort'):
|
||||||
|
|
||||||
|
ns['sort'] = 'id'
|
||||||
|
|
||||||
|
|
||||||
|
sql = '''select a.*, b.pool_type_text, c.status_text
|
||||||
|
from (select * from compute_pool where 1=1 [[filterstr]]) a left join (select k as pool_type,
|
||||||
|
v as pool_type_text from appcodes_kv where parentid='cluster_type') b on a.pool_type = b.pool_type left join (select k as status,
|
||||||
|
v as status_text from appcodes_kv where parentid='product_status') c on a.status = c.status'''
|
||||||
|
|
||||||
|
filterjson = params_kw.get('data_filter')
|
||||||
|
if not filterjson:
|
||||||
|
fields = [ f['name'] for f in [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "resellerid",
|
||||||
|
"title": "商户机构id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"title": "池名称",
|
||||||
|
"type": "str",
|
||||||
|
"length": 128,
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pool_type",
|
||||||
|
"title": "集群类型(k8s/slurm/ray)",
|
||||||
|
"type": "char",
|
||||||
|
"length": 16,
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"title": "描述",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"title": "状态(1=启用 0=停用)",
|
||||||
|
"type": "char",
|
||||||
|
"length": 1,
|
||||||
|
"default": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"title": "创建时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": "no"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"title": "更新时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": "no"
|
||||||
|
}
|
||||||
|
] ]
|
||||||
|
filterjson = default_filterjson(fields, ns)
|
||||||
|
filterdic = ns.copy()
|
||||||
|
filterdic['filterstr'] = ''
|
||||||
|
filterdic['userorgid'] = '${userorgid}$'
|
||||||
|
filterdic['userid'] = '${userid}$'
|
||||||
|
if filterjson:
|
||||||
|
dbf = DBFilter(filterjson)
|
||||||
|
conds = dbf.gen(ns)
|
||||||
|
if conds:
|
||||||
|
ns.update(dbf.consts)
|
||||||
|
conds = f' and {conds}'
|
||||||
|
filterdic['filterstr'] = conds
|
||||||
|
ac = ArgsConvert('[[', ']]')
|
||||||
|
vars = ac.findAllVariables(sql)
|
||||||
|
NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' }
|
||||||
|
filterdic.update(NameSpace)
|
||||||
|
sql = ac.convert(sql, filterdic)
|
||||||
|
|
||||||
|
debug(f'{sql=}')
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.sqlPaging(sql, ns)
|
||||||
|
return r
|
||||||
|
return {
|
||||||
|
"total":0,
|
||||||
|
"rows":[]
|
||||||
|
}
|
||||||
191
wwwroot/compute_pool_list/index.ui
Normal file
191
wwwroot/compute_pool_list/index.ui
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"id":"compute_pool_tbl",
|
||||||
|
"widgettype":"Tabular",
|
||||||
|
"options":{
|
||||||
|
"width":"100%",
|
||||||
|
"height":"100%",
|
||||||
|
|
||||||
|
|
||||||
|
"title":"算力池",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"toolbar":{
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "view_nodes",
|
||||||
|
"label": "查看节点",
|
||||||
|
"icon": "",
|
||||||
|
"type": "link",
|
||||||
|
"url": "/pcpool/compute_node_list/index.ui?pool_id={{id}}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"css":"card",
|
||||||
|
|
||||||
|
|
||||||
|
"editable":{
|
||||||
|
|
||||||
|
"new_data_url":"{{entire_url('add_compute_pool.dspy')}}",
|
||||||
|
|
||||||
|
|
||||||
|
"delete_data_url":"{{entire_url('delete_compute_pool.dspy')}}",
|
||||||
|
|
||||||
|
|
||||||
|
"update_data_url":"{{entire_url('update_compute_pool.dspy')}}"
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
"data_url":"{{entire_url('./get_compute_pool.dspy')}}",
|
||||||
|
|
||||||
|
"data_method":"GET",
|
||||||
|
"data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}},
|
||||||
|
"row_options":{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"browserfields": {
|
||||||
|
"name": {
|
||||||
|
"title": "池名称",
|
||||||
|
"width": 150
|
||||||
|
},
|
||||||
|
"pool_type": {
|
||||||
|
"title": "集群类型",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"title": "状态",
|
||||||
|
"width": 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
"editexclouded":[
|
||||||
|
"id",
|
||||||
|
"resellerid",
|
||||||
|
"description",
|
||||||
|
"created_at",
|
||||||
|
"updated_at"
|
||||||
|
],
|
||||||
|
|
||||||
|
"fields":[
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"title": "id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": "no",
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "resellerid",
|
||||||
|
"title": "商户机构id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"nullable": "no",
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "商户机构id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"title": "池名称",
|
||||||
|
"type": "str",
|
||||||
|
"length": 128,
|
||||||
|
"nullable": "no",
|
||||||
|
"cwidth": 18,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "str",
|
||||||
|
"label": "池名称"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pool_type",
|
||||||
|
"title": "集群类型(k8s/slurm/ray)",
|
||||||
|
"type": "char",
|
||||||
|
"length": 16,
|
||||||
|
"nullable": "no",
|
||||||
|
"label": "集群类型(k8s/slurm/ray)",
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "pool_type",
|
||||||
|
"textField": "pool_type_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "{{get_module_dbname('pcpool')}}",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"tblvalue": "k",
|
||||||
|
"tbltext": "v",
|
||||||
|
"valueField": "pool_type",
|
||||||
|
"textField": "pool_type_text",
|
||||||
|
"cond": "parentid='cluster_type'"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "description",
|
||||||
|
"title": "描述",
|
||||||
|
"type": "text",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "text",
|
||||||
|
"datatype": "text",
|
||||||
|
"label": "描述"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"title": "状态(1=启用 0=停用)",
|
||||||
|
"type": "char",
|
||||||
|
"length": 1,
|
||||||
|
"default": "1",
|
||||||
|
"label": "状态(1=启用 0=停用)",
|
||||||
|
"uitype": "code",
|
||||||
|
"valueField": "status",
|
||||||
|
"textField": "status_text",
|
||||||
|
"params": {
|
||||||
|
"dbname": "{{get_module_dbname('pcpool')}}",
|
||||||
|
"table": "appcodes_kv",
|
||||||
|
"tblvalue": "k",
|
||||||
|
"tbltext": "v",
|
||||||
|
"valueField": "status",
|
||||||
|
"textField": "status_text",
|
||||||
|
"cond": "parentid='product_status'"
|
||||||
|
},
|
||||||
|
"dataurl": "{{entire_url('/appbase/get_code.dspy')}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at",
|
||||||
|
"title": "创建时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": "no",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "timestamp",
|
||||||
|
"label": "创建时间"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at",
|
||||||
|
"title": "更新时间",
|
||||||
|
"type": "timestamp",
|
||||||
|
"nullable": "no",
|
||||||
|
"length": 0,
|
||||||
|
"uitype": "str",
|
||||||
|
"datatype": "timestamp",
|
||||||
|
"label": "更新时间"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"page_rows":160,
|
||||||
|
"cache_limit":5
|
||||||
|
}
|
||||||
|
|
||||||
|
,"binds":[]
|
||||||
|
|
||||||
|
}
|
||||||
70
wwwroot/compute_pool_list/update_compute_pool.dspy
Normal file
70
wwwroot/compute_pool_list/update_compute_pool.dspy
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
|
||||||
|
ns = params_kw.copy()
|
||||||
|
for k,v in ns.items():
|
||||||
|
if v == 'NaN' or v == 'null':
|
||||||
|
ns[k] = None
|
||||||
|
|
||||||
|
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
if not userorgid:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Authorization Error",
|
||||||
|
"timeout":3,
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"message":"Please login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ns['resellerid'] = userorgid
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_module_dbname('pcpool')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
|
||||||
|
ns1 = {
|
||||||
|
|
||||||
|
"resellerid": userorgid,
|
||||||
|
|
||||||
|
|
||||||
|
"id": params_kw.id
|
||||||
|
}
|
||||||
|
recs = await sor.R('compute_pool', ns1)
|
||||||
|
if len(recs) < 1:
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Update Error",
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"timeout":3,
|
||||||
|
"message":"Record no exist or with wrong ownership"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = await sor.U('compute_pool', ns)
|
||||||
|
debug('update success');
|
||||||
|
return {
|
||||||
|
"widgettype":"Message",
|
||||||
|
"options":{
|
||||||
|
"title":"Update Success",
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"timeout":3,
|
||||||
|
"message":"ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"widgettype":"Error",
|
||||||
|
"options":{
|
||||||
|
"title":"Update Error",
|
||||||
|
"cwidth":16,
|
||||||
|
"cheight":9,
|
||||||
|
"timeout":3,
|
||||||
|
"message":"failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user