commit 57e5c736c00501cd4b745471b12f0c5bdb044fb4 Author: yumoqing Date: Wed Jul 16 14:32:09 2025 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..44db15f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# cpcc + +a computing power center manages client \ No newline at end of file diff --git a/app/cpcc.py b/app/cpcc.py new file mode 100644 index 0000000..f395fe2 --- /dev/null +++ b/app/cpcc.py @@ -0,0 +1,60 @@ +from appPublic.registerfunction import RegisterFunction +from ahserver.webapp import webapp +from ahserver.serverenv import ServerEnv +from appbase.init import load_appbase +from rbac.check_perm import load_rbac + +def UiError(title="出错", message="出错啦", timeout=5): + return { + "widgettype":"Error", + "options":{ + "author":"tr", + "timeout":timeout, + "cwidth":15, + "cheight":10, + "title":title, + "message":message + } + } + +def UiMessage(title="消息", message="后台消息", timeout=5): + return { + "widgettype":"Message", + "options":{ + "author":"tr", + "timeout":timeout, + "cwidth":15, + "cheight":10, + "title":title, + "message":message + } + } + +def get_module_dbname(mname): + return 'cpcc' + +rf = RegisterFunction() +rf.register('get_module_dbname', get_module_dbname) + +def init(): + g = ServerEnv() + g.get_module_dbname = get_module_dbname + load_appbase() + load_rbac() + g.UiError = UiError + g.UiMessage = UiMessage + + # k8sAPI远程指令(目前放到.dspy文件中) + # 看样子cpcc.py文件中没有使用到数据库函数 + # g.cluster_kubeconfig = cluster_kubeconfig + + g.result_dict = { + "status": False, + "info": "failed", + "data": None + } + +if __name__ == '__main__': + webapp(init) + +# sword/111111 \ No newline at end of file diff --git a/conf/config.json b/conf/config.json new file mode 100755 index 0000000..f9b6977 --- /dev/null +++ b/conf/config.json @@ -0,0 +1,81 @@ +{ + "password_key":"!@#$%^&*(*&^%$QWERTYUIqwertyui234567", + "logger":{ + "name":"cpcc", + "levelname":"clientinfo", + "logfile":"$[workdir]$/logs/cpcc.log" + }, + "filesroot":"$[workdir]$/files", + "databases":{ + "cpcc":{ + "driver":"aiomysql", + "async_mode":true, + "coding":"utf8", + "maxconn":100, + "dbname":"cpcc", + "kwargs":{ + "user":"test", + "db":"cpcc", + "password":"QUZVcXg5V1p1STMybG5Ia6mX9D0v7+g=", + "host":"localhost" + } + } + }, + "website":{ + "paths":[ + ["$[workdir]$/wwwroot",""] + ], + "client_max_size":10000, + "host":"0.0.0.0", + "port":9203, + "coding":"utf-8", + "ssl_gg":{ + "crtfile":"$[workdir]$/conf/www.bsppo.com.pem", + "keyfile":"$[workdir]$/conf/www.bsppo.com.key" + }, + "indexes":[ + "index.html", + "index.tmpl", + "index.ui", + "index.dspy", + "index.md" + ], + "startswiths":[ + { + "leading":"/idfile", + "registerfunction":"idFileDownload" + } + ], + "processors":[ + [".ws","ws"], + [".xterm","xterm"], + [".proxy","proxy"], + [".llm", "llm"], + [".llms", "llms"], + [".llma", "llma"], + [".xlsxds","xlsxds"], + [".sqlds","sqlds"], + [".tmpl.js","tmpl"], + [".tmpl.css","tmpl"], + [".html.tmpl","tmpl"], + [".bcrud", "bricks_crud"], + [".tmpl","tmpl"], + [".app","app"], + [".bui","bui"], + [".ui","bui"], + [".dspy","dspy"], + [".md","md"] + ], + "session_max_time":3000, + "session_issue_time":2500, + "session_redis_rrrr":{ + "url":"redis://127.0.0.1:6379" + } + }, + "langMapping":{ + "zh-Hans-CN":"zh-cn", + "zh-CN":"zh-cn", + "en-us":"en", + "en-US":"en" + } +} diff --git a/docs/cpc数据表.xlsx b/docs/cpc数据表.xlsx new file mode 100644 index 0000000..20b0d9b Binary files /dev/null and b/docs/cpc数据表.xlsx differ diff --git a/json/build.sh b/json/build.sh new file mode 100755 index 0000000..aae3368 --- /dev/null +++ b/json/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/bash + +xls2ui -m ../models -o ../wwwroot cpcc *.json diff --git a/json/components.json b/json/components.json new file mode 100644 index 0000000..d32b941 --- /dev/null +++ b/json/components.json @@ -0,0 +1,13 @@ +{ + "tblname":"components", + "params":{ + "browserfields": { + "exclouded": ["id"], + "alters": {} + }, + "editexclouded": [ + "id" + ] + } +} + diff --git a/json/cpccluster.json b/json/cpccluster.json new file mode 100644 index 0000000..7a82651 --- /dev/null +++ b/json/cpccluster.json @@ -0,0 +1,13 @@ +{ + "tblname":"cpccluster", + "params":{ + "browserfields": { + "exclouded": ["id", "cpcid"], + "alters": {} + }, + "editexclouded": [ + "id", "cpcid" + ] + } +} + diff --git a/json/cpclist.json b/json/cpclist.json new file mode 100644 index 0000000..b1685f9 --- /dev/null +++ b/json/cpclist.json @@ -0,0 +1,58 @@ +{ + "tblname":"cpclist", + "params":{ + "toolbar":{ + "tools":[ + { + "name":"newcluster", + "selected_row":true, + "label":"新建集群" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"newcluster", + "actiontype":"urlwidget", + "target":"PopupWindow", + "popup_options":{ + "width":"80%", + "height":"80%" + }, + "options":{ + "url":"{{entire_url('../handy/new_cluster.ui')}}", + "params":{ + "id":"{{params_kw.id}}" + } + } + } + ], + "logined_userorgid":"orgid", + "confidential_fields":[ + "api_pwd" + ], + "browserfields": { + "exclouded": [ + "id","orgid", "pcapi_url","pcapi_user", "pcapi_pwd" + ], + "alters": {} + }, + "editexclouded": [ + "id", "orgid" + ], + "subtables":[ + { + "subtable":"cpccluster", + "field":"cpcid", + "title":"集群" + }, + { + "subtable":"cpcnode", + "field":"cpcid", + "title":"节点" + } + ] + } +} + diff --git a/json/cpcnode.json b/json/cpcnode.json new file mode 100644 index 0000000..2658de0 --- /dev/null +++ b/json/cpcnode.json @@ -0,0 +1,16 @@ +{ + "tblname":"cpcnode", + "params":{ + "confidential_fields":["adminpwd"], + "browserfields": { + "exclouded": [ + "id", "cpcid" + ], + "alters": {} + }, + "editexclouded": [ + "id", "cpcid", "clusterid" + ] + } +} + diff --git a/models/components.xlsx b/models/components.xlsx new file mode 100644 index 0000000..644b100 Binary files /dev/null and b/models/components.xlsx differ diff --git a/models/cpccluster.xlsx b/models/cpccluster.xlsx new file mode 100644 index 0000000..22cae5e Binary files /dev/null and b/models/cpccluster.xlsx differ diff --git a/models/cpclist.xlsx b/models/cpclist.xlsx new file mode 100644 index 0000000..321d56b Binary files /dev/null and b/models/cpclist.xlsx differ diff --git a/models/cpcnode.xlsx b/models/cpcnode.xlsx new file mode 100644 index 0000000..10435e6 Binary files /dev/null and b/models/cpcnode.xlsx differ diff --git a/models/cpcnode_config.xlsx b/models/cpcnode_config.xlsx new file mode 100644 index 0000000..a7a8c47 Binary files /dev/null and b/models/cpcnode_config.xlsx differ diff --git a/models/cpcnode_config_detail.xlsx b/models/cpcnode_config_detail.xlsx new file mode 100644 index 0000000..b16d3ad Binary files /dev/null and b/models/cpcnode_config_detail.xlsx differ diff --git a/models/cpvalue.xlsx b/models/cpvalue.xlsx new file mode 100644 index 0000000..01ec09e Binary files /dev/null and b/models/cpvalue.xlsx differ diff --git a/models/mysql.ddl.sql b/models/mysql.ddl.sql new file mode 100644 index 0000000..780c9b4 --- /dev/null +++ b/models/mysql.ddl.sql @@ -0,0 +1,201 @@ + +-- ./cpcnode_config.xlsx + + + + + +drop table if exists cpcnode_config; +CREATE TABLE cpcnode_config +( + + `id` VARCHAR(32) comment 'id', + `nodeid` VARCHAR(32) comment '节点名称', + `name` VARCHAR(255) comment '名称', + `description` VARCHAR(3000) comment '描述' + + +,primary key(id) + + +) +engine=innodb +default charset=utf8 +comment '节点配置表' +; + + +-- ./cpccluster.xlsx + + + + + +drop table if exists cpccluster; +CREATE TABLE cpccluster +( + + `id` VARCHAR(32) comment 'id', + `name` VARCHAR(255) comment '名称', + `cpcid` VARCHAR(32) NOT NULL comment '算力中心id', + `clustertype` VARCHAR(1) comment '集群类型', + `controllerid` VARCHAR(32) comment '控制节点id', + `enable_date` date comment '启用日期', + `export_date` date comment '停用日期' + + +,primary key(id) + + +) +engine=innodb +default charset=utf8 +comment '算力集群' +; + + +-- ./cpcnode.xlsx + + + + + +drop table if exists cpcnode; +CREATE TABLE cpcnode +( + + `id` VARCHAR(32) comment 'id', + `name` VARCHAR(255) comment '名称', + `ip` VARCHAR(90) comment '内网ip', + `sshport` int comment 'ssh端口号', + `adminuser` VARCHAR(99) comment '管理账号', + `adminpwd` VARCHAR(99) comment '管理密码', + `cpcid` VARCHAR(32) NOT NULL comment '算力中心id', + `node_status` VARCHAR(1) comment '节点状态', + `clusterid` int comment '所属集群', + `enable_date` date comment '启用日期', + `export_date` date comment '停用日期' + + +,primary key(id) + + +) +engine=innodb +default charset=utf8 +comment '算力节点' +; + + +-- ./cpvalue.xlsx + + + + + +drop table if exists cpvalue; +CREATE TABLE cpvalue +( + + `id` VARCHAR(32) comment 'id', + `comid` VARCHAR(255) comment '部件id', + `cpval` double(18,3) comment '算力值', + `cpunit` VARCHAR(1) comment '算力单位' + + +,primary key(id) + + +) +engine=innodb +default charset=utf8 +comment '算力值' +; + + +-- ./cpclist.xlsx + + + + + +drop table if exists cpclist; +CREATE TABLE cpclist +( + + `id` VARCHAR(32) comment 'id', + `name` VARCHAR(255) comment '名称', + `orgid` VARCHAR(32) comment '属主机构id', + `pcapi_url` VARCHAR(500) comment 'pcapi网址', + `api_user` VARCHAR(100) comment '接口用户', + `api_pwd` VARCHAR(100) comment '接口密码', + `enable_date` date comment '启用日期', + `expire_date` date comment '停用日期', + `contactname` VARCHAR(100) comment '联系人', + `contactphone` VARCHAR(100) comment '联系电话' + + +,primary key(id) + + +) +engine=innodb +default charset=utf8 +comment '算力中心列表' +; + + +-- ./components.xlsx + + + + + +drop table if exists components; +CREATE TABLE components +( + + `id` VARCHAR(32) comment 'id', + `name` VARCHAR(255) comment '名称', + `ccatelogid` VARCHAR(255) comment '部件分类', + `cmodel` VARCHAR(255) comment '型号', + `unitname` VARCHAR(32) comment '计量名', + `unitvalue` int comment '计量值' + + +,primary key(id) + + +) +engine=innodb +default charset=utf8 +comment '部件表' +; + + +-- ./cpcnode_config_detail.xlsx + + + + + +drop table if exists cpcnode_config_detail; +CREATE TABLE cpcnode_config_detail +( + + `id` VARCHAR(32) comment 'id', + `nodeconfigid` VARCHAR(32) comment '节点配置id', + `comid` VARCHAR(32) comment '部件id', + `comcnt` int comment '部件数量' + + +,primary key(id) + + +) +engine=innodb +default charset=utf8 +comment '节点配置明细项' +; + + diff --git a/script/cpcc_roleperm.sh b/script/cpcc_roleperm.sh new file mode 100644 index 0000000..94bc48e --- /dev/null +++ b/script/cpcc_roleperm.sh @@ -0,0 +1,3 @@ +#!/usr/bin/bash + +python ~/py/rbac/script/roleperm.py sage cpcc reseller operator cpclist cpcnode cpccluster components diff --git a/wwwroot/app_panel.ui b/wwwroot/app_panel.ui new file mode 100644 index 0000000..98a920f --- /dev/null +++ b/wwwroot/app_panel.ui @@ -0,0 +1,40 @@ +{ + "widgettype":"HBox", + "options":{ + "css":"filler" + }, + "subwidgets":[ + { + "widgettype":"Text", + "options":{ + "text":"主菜单", + "tip":"进入主菜单", + "css":"clickable" + }, + "binds":[ + { + "wid":"self", + "event":"click", + "actiontype":"urlwidget", + "target":"Popup", + "popup_options":{ + "eventpos":true, + "dismiss_events":["command"] + }, + "options":{ + "url":"{{entire_url('menu.ui')}}" + } + } + ] + }, + { + "widgettype":"Title4", + "options":{ + "i18n":true, + "otext":"大模型应用平台", + "wrap":true, + "halign":"left" + } + } + ] +} diff --git a/wwwroot/bottom.ui b/wwwroot/bottom.ui new file mode 100644 index 0000000..0158542 --- /dev/null +++ b/wwwroot/bottom.ui @@ -0,0 +1,17 @@ +{ + "widgettype":"HBox", + "options":{ + "cheight":2, + "bgcolor":"#e5e5e5" + }, + "subwidgets":[ + { + "widgettype":"Text", + "options":{ + "otext":"© 2024 版权所有, 开元云(北京)科技有限公司", + "i18n":true, + "wrap":true + } + } + ] +} diff --git a/wwwroot/center.ui b/wwwroot/center.ui new file mode 100644 index 0000000..d5cdc5d --- /dev/null +++ b/wwwroot/center.ui @@ -0,0 +1,15 @@ +{ + "widgettype":"VBox", + "id":"page_center", + "options":{ + "css":"filler" + }, + "subwidgets":[ + { + "widgettype":"urlwidget", + "options":{ + "url":"{{entire_url('/appbase/appcodes')}}" + } + } + ] +} diff --git a/wwwroot/components/add_components.dspy b/wwwroot/components/add_components.dspy new file mode 100644 index 0000000..88e19b6 --- /dev/null +++ b/wwwroot/components/add_components.dspy @@ -0,0 +1,35 @@ + +ns = params_kw.copy() +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('components', 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" + } +} \ No newline at end of file diff --git a/wwwroot/components/delete_components.dspy b/wwwroot/components/delete_components.dspy new file mode 100644 index 0000000..8e18ccf --- /dev/null +++ b/wwwroot/components/delete_components.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('components', 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" + } +} \ No newline at end of file diff --git a/wwwroot/components/get_components.dspy b/wwwroot/components/get_components.dspy new file mode 100644 index 0000000..98e93a1 --- /dev/null +++ b/wwwroot/components/get_components.dspy @@ -0,0 +1,83 @@ + +ns = params_kw.copy() + + +debug(f'get_components.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + ns['sort'] = 'id' + + +sql = '''select a.*, b.ccatelogid_text +from (select * from components where 1=1 [[filterstr]]) a left join (select k as ccatelogid, + v as ccatelogid_text from appcodes_kv where parentid='ccatelog') b on a.ccatelogid = b.ccatelogid''' + +filterjson = params_kw.get('data_filter') +if not filterjson: + fields = [ f['name'] for f in [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 255 + }, + { + "name": "ccatelogid", + "title": "部件分类", + "type": "str", + "length": 255 + }, + { + "name": "cmodel", + "title": "型号", + "type": "str", + "length": 255 + }, + { + "name": "unitname", + "title": "计量名", + "type": "str", + "length": 32 + }, + { + "name": "unitvalue", + "title": "计量值", + "type": "short" + } +] ] + 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 = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/components/index.ui b/wwwroot/components/index.ui new file mode 100644 index 0000000..4d874a1 --- /dev/null +++ b/wwwroot/components/index.ui @@ -0,0 +1,122 @@ + +{ + "id":"components_tbl", + "widgettype":"Tabular", + "options":{ + + + "title":"部件表", + + + + + "css":"card", + + "editable":{ + "new_data_url":"{{entire_url('add_components.dspy')}}", + "delete_data_url":"{{entire_url('delete_components.dspy')}}", + "update_data_url":"{{entire_url('update_components.dspy')}}" + }, + + + "data_url":"{{entire_url('./get_components.dspy')}}", + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id" + ], + "alters": {} +}, + + + "editexclouded":[ + "id" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 255, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "名称" + }, + { + "name": "ccatelogid", + "title": "部件分类", + "type": "str", + "length": 255, + "label": "部件分类", + "uitype": "code", + "valueField": "ccatelogid", + "textField": "ccatelogid_text", + "params": { + "dbname": "{{rfexe('get_module_dbname', 'cpcc')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "ccatelogid", + "textField": "ccatelogid_text", + "cond": "parentid='ccatelog'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "cmodel", + "title": "型号", + "type": "str", + "length": 255, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "型号" + }, + { + "name": "unitname", + "title": "计量名", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "计量名" + }, + { + "name": "unitvalue", + "title": "计量值", + "type": "short", + "length": 0, + "uitype": "int", + "datatype": "short", + "label": "计量值" + } +] + }, + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +} \ No newline at end of file diff --git a/wwwroot/components/update_components.dspy b/wwwroot/components/update_components.dspy new file mode 100644 index 0000000..6362a54 --- /dev/null +++ b/wwwroot/components/update_components.dspy @@ -0,0 +1,32 @@ + +ns = params_kw.copy() + + + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.U('components', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpccluster/add_cpccluster.dspy b/wwwroot/cpccluster/add_cpccluster.dspy new file mode 100644 index 0000000..361a2ba --- /dev/null +++ b/wwwroot/cpccluster/add_cpccluster.dspy @@ -0,0 +1,35 @@ + +ns = params_kw.copy() +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('cpccluster', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpccluster/delete_cpccluster.dspy b/wwwroot/cpccluster/delete_cpccluster.dspy new file mode 100644 index 0000000..3d40fe8 --- /dev/null +++ b/wwwroot/cpccluster/delete_cpccluster.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('cpccluster', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpccluster/get_cpccluster.dspy b/wwwroot/cpccluster/get_cpccluster.dspy new file mode 100644 index 0000000..c69433b --- /dev/null +++ b/wwwroot/cpccluster/get_cpccluster.dspy @@ -0,0 +1,91 @@ + +ns = params_kw.copy() + + +debug(f'get_cpccluster.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + ns['sort'] = 'id' + +sql = '''select a.*, b.clustertype_text, c.controllerid_text +from (select * from cpccluster where 1=1 [[filterstr]]) a left join (select k as clustertype, + v as clustertype_text from appcodes_kv where parentid='clustertype') b on a.clustertype = b.clustertype left join (select id as controllerid, + ip as controllerid_text from cpcnode where 1 = 1) c on a.controllerid = c.controllerid''' + +filterjson = params_kw.get('data_filter') +if not filterjson: + fields = [ f['name'] for f in [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 255 + }, + { + "name": "cpcid", + "title": "算力中心id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "clustertype", + "title": "集群类型", + "type": "str", + "length": 1 + }, + { + "name": "controllerid", + "title": "控制节点id", + "type": "str", + "length": 32 + }, + { + "name": "enable_date", + "title": "启用日期", + "type": "date", + "length": 255 + }, + { + "name": "export_date", + "title": "停用日期", + "type": "date", + "length": 255 + } +] ] + 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 = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/cpccluster/index.ui b/wwwroot/cpccluster/index.ui new file mode 100644 index 0000000..1725fd3 --- /dev/null +++ b/wwwroot/cpccluster/index.ui @@ -0,0 +1,239 @@ +{ + "id": "cpccluster_tbl", + "widgettype": "Tabular", + "options": { + "title": "算力集群", + "css": "card", + "toolbar": { + "tools": [ + { + "name": "newworker", + "selected_row": true, + "label": "新增工作节点" + }, + { + "name": "newpodyaml", + "selected_row": true, + "label": "新建资源YAML" + } + ], + "css": "float-right" + }, + "editable": { + "new_data_url": "{{entire_url('add_cpccluster.dspy')}}", + "delete_data_url": "{{entire_url('delete_cpccluster.dspy')}}", + "update_data_url": "{{entire_url('update_cpccluster.dspy')}}" + }, + "data_url": "{{entire_url('./get_cpccluster.dspy')}}", + "data_method": "GET", + "data_params": {{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options": { + "browserfields": { + "exclouded": [ + "id", + "cpcid", + "clusterid" + ], + "alters": {} + }, + "editexclouded": [ + "id", + "cpcid", + "clusterid" + ], + "fields": [ + { + "name": "name", + "title": "集群名称", + "type": "str", + "length": 255, + "cwidth": 14, + "uitype": "str", + "datatype": "str", + "label": "集群名称" + }, + { + "name": "controllerid", + "title": "控制节点IP", + "type": "str", + "length": 32, + "cwidth": 12, + "label": "控制节点IP", + "uitype": "code", + "valueField": "controllerid", + "textField": "controllerid_text", + "params": { + "dbname": "{{rfexe('get_module_dbname', 'cpcc')}}", + "table": "cpcnode", + "tblvalue": "id", + "tbltext": "name", + "valueField": "controllerid", + "textField": "controllerid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "cpcid", + "title": "算力中心id", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "算力中心id" + }, + { + "name": "clustertype", + "title": "集群类型", + "type": "str", + "length": 1, + "cwidth": 8, + "label": "集群类型", + "uitype": "code", + "valueField": "clustertype", + "textField": "clustertype_text", + "params": { + "dbname": "{{rfexe('get_module_dbname', 'cpcc')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "clustertype", + "textField": "clustertype_text", + "cond": "parentid='clustertype'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "clusterjoin", + "title": "加入集群凭证", + "type": "str", + "length": 255, + "cwidth": 45, + "uitype": "str", + "datatype": "str", + "label": "加入集群凭证" + }, + { + "name": "enable_date", + "title": "启用日期", + "type": "date", + "length": 0, + "cwidth": 8, + "uitype": "date", + "datatype": "date", + "label": "启用日期" + }, + { + "name": "export_date", + "title": "停用日期", + "type": "date", + "length": 0, + "cwidth": 8, + "uitype": "date", + "datatype": "date", + "label": "停用日期" + } + ] + }, + "content_view": { + "widgettype": "TabPanel", + "options": { + "tab_wide": "auto", + "height": "100%", + "width": "100%", + "tab_pos": "top", + "items": [ + { + "name": "cpcpod", + "label": "实时资源实例", + "content": { + "widgettype": "urlwidget", + "options": { + "params": { + "clusterid": "${id}", + "cpcid": "${cpcid}" + }, + "url": "{{entire_url('..\/cpcpod')}}" + } + } + }, + { + "name": "cpcworker", + "label": "实时集群节点", + "content": { + "widgettype": "urlwidget", + "options": { + "params": { + "clusterid": "${id}", + "cpcid": "${cpcid}" + }, + "url": "{{entire_url('..\/cpcworker')}}" + } + } + }, + { + "name": "cpcyamlconfig", + "label": "资源实例配置", + "content": { + "widgettype": "urlwidget", + "options": { + "params": { + "clusterid": "${id}", + "cpcid": "${cpcid}" + }, + "url": "{{entire_url('..\/cpcpodyaml')}}" + } + } + } + ] + } + }, + "page_rows": 160, + "cache_limit": 5 + }, + "binds": [ + { + "wid": "self", + "event": "newworker", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "width": "80%", + "height": "80%" + }, + "options": { + "url": "{{entire_url('../cpcworker/new_worker.ui')}}", + "params": { + "id": "{{params_kw.id}}" + } + } + }, + { + "wid": "self", + "event": "newpodyaml", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "width": "80%", + "height": "80%" + }, + "options": { + "url": "{{entire_url('../cpcpod/new_podyaml.ui')}}", + "params": { + "id": "{{params_kw.id}}" + } + } + } + ] +} \ No newline at end of file diff --git a/wwwroot/cpccluster/update_cpccluster.dspy b/wwwroot/cpccluster/update_cpccluster.dspy new file mode 100644 index 0000000..af390dd --- /dev/null +++ b/wwwroot/cpccluster/update_cpccluster.dspy @@ -0,0 +1,32 @@ + +ns = params_kw.copy() + + + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.U('cpccluster', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpclist/add_cpclist.dspy b/wwwroot/cpclist/add_cpclist.dspy new file mode 100644 index 0000000..55b2d24 --- /dev/null +++ b/wwwroot/cpclist/add_cpclist.dspy @@ -0,0 +1,52 @@ + +ns = params_kw.copy() +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + +if params_kw.get('api_pwd'): + ns['api_pwd'] = password_encode(params_kw.get('api_pwd')) + + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['orgid'] = userorgid + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('cpclist', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpclist/delete_cpclist.dspy b/wwwroot/cpclist/delete_cpclist.dspy new file mode 100644 index 0000000..f9ab4b6 --- /dev/null +++ b/wwwroot/cpclist/delete_cpclist.dspy @@ -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['orgid'] = userorgid + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('cpclist', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpclist/get_cpclist.dspy b/wwwroot/cpclist/get_cpclist.dspy new file mode 100644 index 0000000..f6bd8ea --- /dev/null +++ b/wwwroot/cpclist/get_cpclist.dspy @@ -0,0 +1,123 @@ + +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['orgid'] = userorgid +ns['userorgid'] = userorgid + +debug(f'get_cpclist.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + ns['sort'] = 'id' + + +sql = '''select a.*, b.orgid_text +from (select * from cpclist where 1=1 [[filterstr]]) a left join (select id as orgid, + orgname as orgid_text from organization where 1 = 1) b on a.orgid = b.orgid''' + +filterjson = params_kw.get('data_filter') +if not filterjson: + fields = [ f['name'] for f in [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 255 + }, + { + "name": "orgid", + "title": "属主机构id", + "type": "str", + "length": 32 + }, + { + "name": "pcapi_url", + "title": "pcapi网址", + "type": "str", + "length": 500 + }, + { + "name": "api_user", + "title": "接口用户", + "type": "str", + "length": 100 + }, + { + "name": "api_pwd", + "title": "接口密码", + "type": "str", + "length": 100 + }, + { + "name": "enable_date", + "title": "启用日期", + "type": "date", + "length": 255 + }, + { + "name": "expire_date", + "title": "停用日期", + "type": "date", + "length": 255 + }, + { + "name": "contactname", + "title": "联系人", + "type": "str", + "length": 100 + }, + { + "name": "contactphone", + "title": "联系电话", + "type": "str", + "length": 100 + } +] ] + 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 = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/cpclist/index.ui b/wwwroot/cpclist/index.ui new file mode 100644 index 0000000..a6edc72 --- /dev/null +++ b/wwwroot/cpclist/index.ui @@ -0,0 +1,209 @@ +{ + "id": "cpclist_tbl", + "widgettype": "Tabular", + "options": { + "title": "算力中心列表", + "toolbar": { + "tools": [ + { + "name": "newcluster", + "selected_row": true, + "label": "新建集群" + } + ] + }, + "css": "card", + "editable": { + "new_data_url": "{{entire_url('add_cpclist.dspy')}}", + "delete_data_url": "{{entire_url('delete_cpclist.dspy')}}", + "update_data_url": "{{entire_url('update_cpclist.dspy')}}" + }, + "data_url": "{{entire_url('./get_cpclist.dspy')}}", + "data_method": "GET", + "data_params": {{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options": { + "browserfields": { + "exclouded": [ + "id", + "orgid", + "pcapi_url", + "pcapi_user", + "pcapi_pwd" + ], + "alters": {} + }, + "editexclouded": [ + "id", + "orgid" + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 255, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "名称" + }, + { + "name": "orgid", + "title": "属主机构id", + "type": "str", + "length": 32, + "label": "属主机构id", + "uitype": "code", + "valueField": "orgid", + "textField": "orgid_text", + "params": { + "dbname": "{{rfexe('get_module_dbname', 'cpcc')}}", + "table": "organization", + "tblvalue": "id", + "tbltext": "orgname", + "valueField": "orgid", + "textField": "orgid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "pcapi_url", + "title": "pcapi网址", + "type": "str", + "length": 500, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "pcapi网址" + }, + { + "name": "api_user", + "title": "接口用户", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "接口用户" + }, + { + "name": "api_pwd", + "title": "接口密码", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "接口密码" + }, + { + "name": "enable_date", + "title": "启用日期", + "type": "date", + "length": 0, + "cwidth": 18, + "uitype": "date", + "datatype": "date", + "label": "启用日期" + }, + { + "name": "expire_date", + "title": "停用日期", + "type": "date", + "length": 0, + "cwidth": 18, + "uitype": "date", + "datatype": "date", + "label": "停用日期" + }, + { + "name": "contactname", + "title": "联系人", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "联系人" + }, + { + "name": "contactphone", + "title": "联系电话", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "联系电话" + } + ] + }, + "content_view": { + "widgettype": "TabPanel", + "options": { + "tab_wide": "auto", + "height": "100%", + "width": "100%", + "tab_pos": "top", + "items": [ + { + "name": "cpccluster", + "label": "集群", + "content": { + "widgettype": "urlwidget", + "options": { + "params": { + "cpcid": "${id}" + }, + "url": "{{entire_url('..\/cpccluster')}}" + } + } + }, + { + "name": "cpcnode", + "label": "节点", + "content": { + "widgettype": "urlwidget", + "options": { + "params": { + "cpcid": "${id}" + }, + "url": "{{entire_url('..\/cpcnode')}}" + } + } + } + ] + } + }, + "page_rows": 160, + "cache_limit": 5 + }, + "binds": [ + { + "wid": "self", + "event": "newcluster", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "width": "80%", + "height": "80%" + }, + "options": { + "url": "{{entire_url('../handy/new_cluster.ui')}}", + "params": { + "id": "{{params_kw.id}}" + } + } + } + ] +} \ No newline at end of file diff --git a/wwwroot/cpclist/update_cpclist.dspy b/wwwroot/cpclist/update_cpclist.dspy new file mode 100644 index 0000000..8325779 --- /dev/null +++ b/wwwroot/cpclist/update_cpclist.dspy @@ -0,0 +1,49 @@ + +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['orgid'] = userorgid + + +if params_kw.get('api_pwd'): + ns['api_pwd'] = password_encode(params_kw.get('api_pwd')) + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.U('cpclist', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpcnode/add_cpcnode.dspy b/wwwroot/cpcnode/add_cpcnode.dspy new file mode 100644 index 0000000..3493aa7 --- /dev/null +++ b/wwwroot/cpcnode/add_cpcnode.dspy @@ -0,0 +1,38 @@ + +ns = params_kw.copy() +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + +if params_kw.get('adminpwd'): + ns['adminpwd'] = password_encode(params_kw.get('adminpwd')) + + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('cpcnode', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpcnode/delete_cpcnode.dspy b/wwwroot/cpcnode/delete_cpcnode.dspy new file mode 100644 index 0000000..a9d4f04 --- /dev/null +++ b/wwwroot/cpcnode/delete_cpcnode.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('cpcnode', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpcnode/get_cpcnode.dspy b/wwwroot/cpcnode/get_cpcnode.dspy new file mode 100644 index 0000000..e6c5f5a --- /dev/null +++ b/wwwroot/cpcnode/get_cpcnode.dspy @@ -0,0 +1,126 @@ + +ns = params_kw.copy() + + +debug(f'get_cpcnode.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + ns['sort'] = 'id' + + +sql = '''select a.*, b.node_status_text,d.devicetype_text +from (select * from cpcnode where 1=1 [[filterstr]]) +a left join (select k as node_status, v as node_status_text from appcodes_kv where parentid='node_status') +b on a.node_status = b.node_status +left join (select k as devicetype, v as devicetype_text from appcodes_kv where parentid = 'devicetype') +d on a.devicetype = d.devicetype''' + + +filterjson = params_kw.get('data_filter') +if not filterjson: + fields = [ f['name'] for f in [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 255 + }, + { + "name": "devicetype", + "title": "设备类型", + "type": "str", + "length": 1 + }, + { + "name": "ip", + "title": "内网ip", + "type": "str", + "length": 90 + }, + { + "name": "sshport", + "title": "ssh端口号", + "type": "short" + }, + { + "name": "adminuser", + "title": "管理账号", + "type": "str", + "length": 99 + }, + { + "name": "adminpwd", + "title": "管理密码", + "type": "str", + "length": 99 + }, + { + "name": "cpcid", + "title": "算力中心id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "node_status", + "title": "节点状态", + "type": "str", + "length": 1, + "nullable": "yes" + }, + { + "name": "clusterid", + "title": "所属集群", + "type": "long", + "length": 32, + "nullable": "yes" + }, + { + "name": "enable_date", + "title": "启用日期", + "type": "date", + "length": 255 + }, + { + "name": "export_date", + "title": "停用日期", + "type": "date", + "length": 255 + } +] ] + 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 = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/cpcnode/index.ui b/wwwroot/cpcnode/index.ui new file mode 100644 index 0000000..c689cd3 --- /dev/null +++ b/wwwroot/cpcnode/index.ui @@ -0,0 +1,179 @@ +{ + "id": "cpcnode_tbl", + "widgettype": "Tabular", + "options": { + "title": "入网算力节点", + "css": "card", + "editable": { + "new_data_url": "{{entire_url('add_cpcnode.dspy')}}", + "delete_data_url": "{{entire_url('delete_cpcnode.dspy')}}", + "update_data_url": "{{entire_url('update_cpcnode.dspy')}}" + }, + "data_url": "{{entire_url('./get_cpcnode.dspy')}}", + "data_method": "GET", + "data_params": {{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options": { + "browserfields": { + "exclouded": [ + "id", + "cpcid", + "clusterid" + ], + "alters": {} + }, + "editexclouded": [ + "id", + "cpcid", + "clusterid" + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "ip", + "title": "内网ip", + "type": "str", + "length": 90, + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "内网ip" + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 255, + "cwidth": 16, + "uitype": "str", + "datatype": "str", + "label": "名称" + }, + { + "name": "devicetype", + "title": "设备类型", + "type": "str", + "length": 1, + "cwidth": 8, + "label": "设备类型", + "uitype": "code", + "valueField": "devicetype", + "textField": "devicetype_text", + "params": { + "dbname": "{{rfexe('get_module_dbname', 'cpcc')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "devicetype", + "textField": "devicetype_text", + "cond": "parentid='devicetype'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "sshport", + "title": "ssh端口号", + "type": "short", + "length": 0, + "uitype": "int", + "datatype": "short", + "label": "ssh端口号" + }, + { + "name": "adminuser", + "title": "管理账号", + "type": "str", + "length": 99, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "管理账号" + }, + { + "name": "adminpwd", + "title": "管理密码", + "type": "str", + "length": 99, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "管理密码" + }, + { + "name": "cpcid", + "title": "算力中心id", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "算力中心id" + }, + { + "name": "node_status", + "title": "节点状态", + "type": "str", + "length": 1, + "nullable": "yes", + "label": "节点状态", + "uitype": "code", + "valueField": "node_status", + "textField": "node_status_text", + "params": { + "dbname": "{{rfexe('get_module_dbname', 'cpcc')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "node_status", + "textField": "node_status_text", + "cond": "parentid='node_status'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "clusterid", + "title": "所属集群", + "type": "long", + "length": 0, + "nullable": "yes", + "cwidth": 18, + "uitype": "int", + "datatype": "long", + "label": "所属集群" + }, + { + "name": "enable_date", + "title": "启用日期", + "type": "date", + "length": 0, + "cwidth": 18, + "uitype": "date", + "datatype": "date", + "label": "启用日期" + }, + { + "name": "export_date", + "title": "停用日期", + "type": "date", + "length": 0, + "cwidth": 18, + "uitype": "date", + "datatype": "date", + "label": "停用日期" + } + ] + }, + "page_rows": 160, + "cache_limit": 5 + }, + "binds": [] +} \ No newline at end of file diff --git a/wwwroot/cpcnode/update_cpcnode.dspy b/wwwroot/cpcnode/update_cpcnode.dspy new file mode 100644 index 0000000..137ddcf --- /dev/null +++ b/wwwroot/cpcnode/update_cpcnode.dspy @@ -0,0 +1,35 @@ + +ns = params_kw.copy() + + + +if params_kw.get('adminpwd'): + ns['adminpwd'] = password_encode(params_kw.get('adminpwd')) + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.U('cpcnode', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpcpod/get_cpcpod.dspy b/wwwroot/cpcpod/get_cpcpod.dspy new file mode 100644 index 0000000..428a421 --- /dev/null +++ b/wwwroot/cpcpod/get_cpcpod.dspy @@ -0,0 +1,76 @@ +ns = params_kw.copy() + +debug(f'get_cpcworker.dspy:{ns=}') +#{'_webbricks_': '1', 'width': '1139', 'height': '940', '_is_mobile': '0', +#'clusterid': '4hBm8atruISOU2bs24t_N', 'cpcid': 'AROU9udKtPNyh0AZtO_WY', 'page': '1', 'rows': '160'} + +cpcid = params_kw.cpcid +clusterid = params_kw.clusterid + +# 写数据库的步骤先pass +dbname = get_module_dbname('cpcc') +db = DBPools() + +async with db.sqlorContext(dbname) as sor: + # 通过算力中心ID,获取算力中心部署所在设备的http(s)协议信息 + cpcs = await sor.R('cpclist', {'id':cpcid}) + if len(cpcs) < 1: + e = Exception(f'cpclist {cpcid=} not exists') + exception(f'{e}') + raise e + cpc = cpcs[0] + # 通过集群ID,获取算力集群控制节点所在设备的ssh协议参数 + cpclusters = await sor.R('cpccluster', {'id':clusterid}) + if len(cpclusters) < 1: + e = Exception(f'cpclist {clusterid=} not exists') + exception(f'{e}') + raise e + cpcluster = cpclusters[0] + nodeid = cpcluster.get("controllerid") + kubeconfig = cpcluster.get("kubeconfig") + nodes = await sor.R('cpcnode', {'id':nodeid}) + # 这里有问题:没有匹配到节点信息!!! + if len(nodes) < 1: + e = Exception(f'cpcnode {nodeid=} 节点基础信息不匹配') + exception(f'{e}') + raise e + node = nodes[0] + url = cpc.pcapi_url + "/pcapi/api/v1/cluster/common/get_cluster_pods" + debug(f"请求url: {url=}") + debug(f"目标IP认证信息: {node.ip=} {node.sshport=} {node.adminuser=} {password_decode(node.adminpwd)=}") + # 请求方式待定,取决于获取参数值方式 + + headers = basic_auth_headers(cpc.api_user, password_decode(cpc.api_pwd)) + + hc = HttpClient() + + import requests + params = { + 'host':node.ip, + 'port':node.sshport, + 'user':node.adminuser, + 'psssword':password_decode(node.adminpwd), + 'kubeconfig':kubeconfig + } + debug(f'请求参数{params=}') + + #resp = await hc.request(url, method='GET', + # headers = headers, + # data=params + #) + # 框架不支持超时时间 + resp = requests.post(url, + headers = headers, + data=params, + timeout=500 + ) + resp = json.dumps(resp.json()) #这里模拟hc.request返回的结果写后续逻辑 + #debug(f'{type(resp)=}->{resp=}') + debug(f"pcapi返回值: {json.loads(resp)=}") + data = json.loads(resp).get('data') + if json.loads(resp).get("status") == True: + return data + else: + return UiError(title='get cluster nodes', message='failed') + +return UiError(title='get cluster nodes', message='uncatched error') \ No newline at end of file diff --git a/wwwroot/cpcpod/get_node_labels.dspy b/wwwroot/cpcpod/get_node_labels.dspy new file mode 100644 index 0000000..baca0d7 --- /dev/null +++ b/wwwroot/cpcpod/get_node_labels.dspy @@ -0,0 +1,11 @@ +db = DBPools() +dbname = get_module_dbname('cpcc') +ns = { + "clusterid":params_kw.clusterid +} +async with db.sqlorContext(dbname) as sor: + sql = "select cpcid,id,ip,node_status from cpcnode where cpcid = (select cpcid from cpccluster where id = ${clusterid}$) and node_status = '0';" + recs = await sor.sqlExe(sql, ns.copy()) + return recs +exception(f'{sql=},{ns=}') +return [] \ No newline at end of file diff --git a/wwwroot/cpcpod/index.ui b/wwwroot/cpcpod/index.ui new file mode 100644 index 0000000..ed00628 --- /dev/null +++ b/wwwroot/cpcpod/index.ui @@ -0,0 +1,179 @@ +{ + "id": "cpcpod_tbl", + "widgettype": "Tabular", + "options": { + "title": "集群实时资源实例", + "description":"显示非系统命名空间下实时资源实例", + "css": "card", + "editable": { + "new_data_url": "{{entire_url('add_cpcpod.dspy')}}", + "delete_data_url": "{{entire_url('delete_cpcpod.dspy')}}", + "update_data_url": "{{entire_url('update_cpcpod.dspy')}}" + }, + "data_url": "{{entire_url('./get_cpcpod.dspy')}}", + "data_method": "GET", + "data_params": {{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options": { + "browserfields": { + "exclouded": [ + "id", + "cpcid", + "clusterid" + ], + "alters": {} + }, + "editexclouded": [ + "id", + "cpcid", + "clusterid" + ], + "fields": [ + { + "name": "pod_namespace", + "title": "命名空间", + "type": "str", + "length": 255, + "cwidth": 14, + "uitype": "str", + "datatype": "str", + "label": "命名空间" + }, + { + "name": "pod_name", + "title": "资源实例名称", + "type": "str", + "length": 255, + "cwidth": 20, + "uitype": "str", + "datatype": "str", + "label": "资源实例名称" + }, + { + "name": "pod_ready", + "title": "就绪状态", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "就绪状态" + }, + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "cpcid", + "title": "算力中心id", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "算力中心id" + }, + { + "name": "pod_running", + "title": "运行状态", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 10 , + "uitype": "str", + "datatype": "str", + "label": "运行状态" + }, + { + "name": "pod_restart", + "title": "重启次数", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "重启次数" + }, + { + "name": "pod_age", + "title": "运行时长", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "运行时长" + }, + { + "name": "pod_ip", + "title": "集群内部IP", + "type": "str", + "length": 255, + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "集群内部IP" + }, + { + "name": "pod_node", + "title": "Pod节点主机名", + "type": "str", + "length": 255, + "cwidth": 16, + "uitype": "str", + "datatype": "str", + "label": "Pod节点主机名" + }, + { + "name": "pod_nominated_node", + "title": "Pod指定节点", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "Pod指定节点" + }, + { + "name": "pod_cpurate", + "title": "cpu占用率", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "cpu占用率" + }, + { + "name": "pod_memrate", + "title": "内存占用率", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "内存占用率" + }, + { + "name": "pod_readiness_gates", + "title": "盖茨准备", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "盖茨准备" + } + ] + }, + "page_rows": 160, + "cache_limit": 5 + }, + "binds": [] +} \ No newline at end of file diff --git a/wwwroot/cpcpod/new_cpcpodyaml.dspy b/wwwroot/cpcpod/new_cpcpodyaml.dspy new file mode 100644 index 0000000..9a6302c --- /dev/null +++ b/wwwroot/cpcpod/new_cpcpodyaml.dspy @@ -0,0 +1,101 @@ +# params_kw: +# clusterid +debug(f'Web参数:{params_kw=}') +dbname = get_module_dbname('cpcc') +db = DBPools() +clusterid = params_kw.clusterid + +async with db.sqlorContext(dbname) as sor: + # clusterid -> nodeid + cpclusters = await sor.R('cpccluster', {'id':clusterid}) + if len(cpclusters) < 1: + e = Exception(f'cpclist {clusterid=} not exists') + exception(f'{e}') + raise e + cpcluster = cpclusters[0] + # 此处的nodeid即控制节点id + kubeconfig = cpcluster.get("kubeconfig") + nodeid = cpcluster.get("controllerid") + cpcid = cpcluster.get("cpcid") + cpcs = await sor.R('cpclist', {'id':cpcid}) + if len(cpcs) < 1: + e = Exception(f'cpclist {cpcid=} not exists') + exception(f'{e}') + raise e + cpc = cpcs[0] + nodes = await sor.R('cpcnode', {'id':nodeid}) + # 这里有问题:没有匹配到节点信息!!! + if len(nodes) < 1: + e = Exception(f'cpcnode {nodeid=} 节点基础信息不匹配') + exception(f'{e}') + raise e + node = nodes[0] + url = cpc.pcapi_url + "/pcapi/api/v1/cluster/common/yaml_apply" + debug(f"请求url: {url=}") + debug(f"目标IP认证信息: {node.ip=} {node.sshport=} {node.adminuser=} {password_decode(node.adminpwd)=}") + # 请求方式待定,取决于获取参数值方式 + + userorgid = await get_userorgid() + debug(f'当前组织ID:{userorgid}') + if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } + headers = basic_auth_headers(cpc.api_user, password_decode(cpc.api_pwd)) + ns_name = clusterid.replace("_","-") + "-" + userorgid # 集群信息+组织信息合成命名空间 + ns_name = ns_name.lower() + hc = HttpClient() + # type参数:更新和新增都是为apply,删除为delete + yamlconfig_id = uuid().replace("_","-").lower() + import requests + params = { + 'cluster_type': "k8s", + 'host':node.ip, + 'port':node.sshport, + 'user':node.adminuser, + 'psssword':password_decode(node.adminpwd), + 'kubeconfig':kubeconfig, + 'action':'apply', + 'namespace_name': ns_name, + 'serviceaccount_name': ns_name + "-serviceaccount", + 'podcd_name': yamlconfig_id + "-" + params_kw.get("source_podengine").lower(), + 'service_name': yamlconfig_id + "-service", + 'instance_type': params_kw.get("instance_type") #目前仅支持RelationalDB和LinuxOS + } + params.update(params_kw) + + keys_to_remove = ['_webbricks_', 'width', 'height', '_is_mobile'] + for key in keys_to_remove: + if key in params: + del params[key] + + resp = requests.post(url, + headers = headers, + data=params, + timeout=500 + ) + if resp.status_code != 200: + return UiError(title='新增YAML参数', message=f'算力中心服务异常 {resp.status_code}') + debug(f'{type(resp)=}->{resp=}') + resp = json.dumps(resp.json()) #这里模拟hc.request返回的结果写后续逻辑 + keys_to_remove = ['cluster_type', 'host', 'port', 'user', 'psssword', 'kubeconfig'] + for key in keys_to_remove: + if key in params: + del params[key] + params['id'] = yamlconfig_id + params['cpcid'] = cpcid + if json.loads(resp).get("status") == True: + debug(f"更新资源yaml配置元数据: {params=}") + await sor.C('yaml_config', params) + return UiMessage(title='新增YAML参数', message='资源实例参数更新成功,请10秒后查看实时资源实例面板') + else: + return UiError(title='新增YAML参数', message='资源实例数据写入数据库失败') + +return UiError(title='新增YAML参数', message='全局未知异常') diff --git a/wwwroot/cpcpod/new_podyaml.ui b/wwwroot/cpcpod/new_podyaml.ui new file mode 100644 index 0000000..9674295 --- /dev/null +++ b/wwwroot/cpcpod/new_podyaml.ui @@ -0,0 +1,265 @@ +{ + "widgettype":"Form", + "options":{ + "title":"新建资源YAML模板", + "description":"通过实例化资源YAML,为算力中心的kubernetes集群新增资源实例(注意:kubernetes部分参数值只允许小写,若拼接请以'-'间隔)", + "fields":[ + { + "name":"clusterid", + "value":"{{params_kw.id}}", + "uitype":"hide" + }, + { + "name": "source_name", + "title": "资源容器名称", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "资源容器名称(可以-分隔)" + }, + { + "name": "source_authuser", + "title": "资源初始账号", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "资源初始账号" + }, + { + "name": "source_authpasswd", + "title": "资源初始密码", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "资源初始密码" + }, + { + "name": "source_podengine", + "label":"资源实例控制器", + "data":[ + { + "value":"Deployment", + "text":"Deployment" + }, + { + "value":"Job", + "text":"Job" + }, + { + "value":"CronJob", + "text":"CronJob" + }, + { + "value":"DaemonSet", + "text":"DaemonSet" + }, + { + "value":"StatefulSet", + "text":"StatefulSet" + }, + { + "value":"standalone", + "text":"standalone" + } + ], + "value":"StatefulSet", + "uitype":"code" + }, + { + "name": "source_replicasetnum", + "title": "资源副本个数", + "type": "short", + "length": 0, + "uitype": "int", + "cwidth": 18, + "datatype": "str", + "label": "资源副本个数" + }, + { + "name":"instance_type", + "label":"资源实例类型", + "data":[ + { + "value":"RelationalDB", + "text":"关系型数据库" + }, + { + "value":"LinuxOS", + "text":"Linux操作系统" + } + ], + "value":"LinuxOS", + "uitype":"code" + }, + { + "name":"pod_imagepath", + "label":"基础镜像", + "data":[ + { + "value":"docker.io/library/mysql:8.0", + "text":"docker.io/library/mysql:8.0" + }, + { + "value":"docker.io/library/nginx:latest", + "text":"docker.io/library/nginx:latest" + }, + { + "value":"docker.io/library/ubuntu:22.04", + "text":"docker.io/library/ubuntu:22.04" + }, + { + "value":"docker.io/jupyter/base-notebook:latest", + "text":"docker.io/jupyter/base-notebook:latest" + } + ], + "value":"docker.io/library/ubuntu:22.04", + "uitype":"code" + }, + { + "name": "source_memrate", + "title": "资源内存限制", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "资源内存限制(示例:100Mi)" + }, + { + "name": "source_cpurate", + "title": "资源cpu限制", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "资源cpu限制(示例:100m)" + }, + { + "name": "source_selflabel", + "title": "资源自身标签", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "资源自身标签(service代理)" + }, + { + "name":"source_portmode", + "label":"端口映射模式", + "data":[ + { + "value":"NodePort", + "text":"NodePort" + }, + { + "value":"LoadBalancer", + "text":"LoadBalancer" + }, + { + "value":"Ingress", + "text":"Ingress" + } + ], + "value":"NodePort", + "uitype":"code" + }, + { + "name":"source_restartpolicy", + "label":"重启策略", + "data":[ + { + "value":"Always", + "text":"Always" + }, + { + "value":"OnFailure", + "text":"OnFailure" + }, + { + "value":"Never", + "text":"Never" + } + ], + "value":"Always", + "uitype":"code" + }, + { + "name": "source_apiport", + "title": "集群内部映射端口", + "type": "short", + "length": 0, + "uitype": "int", + "cwidth": 18, + "datatype": "str", + "label": "集群内部映射端口" + }, + { + "name": "source_insideport", + "title": "容器默认监听端口", + "type": "short", + "length": 0, + "uitype": "int", + "cwidth": 18, + "datatype": "str", + "label": "容器默认监听端口" + }, + { + "name": "source_outsideport", + "title": "集群外部映射端口", + "type": "short", + "length": 0, + "uitype": "int", + "cwidth": 18, + "datatype": "str", + "label": "集群外部映射端口(必须是30000-32767之间的整数)" + }, + { + "name":"source_nodeSelector", + "label":"节点标签(指定节点运行)", + "uitype":"code", + "textField":"ip", + "valueField":"id", + "dataurl":"{{entire_url('get_node_labels.dspy')}}?clusterid={{params_kw.id}}" + }, + { + "name":"source_mountpath", + "title":"容器内挂载点", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label":"容器内挂载点" + }, + { + "name": "source_storagelimits", + "title": "定义存储盘容量(单位:Gi)", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "定义存储盘容量(单位:Gi)" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"PopupWindow", + "options":{ + "url":"{{entire_url('new_cpcpodyaml.dspy')}}" + } + } + ] +} diff --git a/wwwroot/cpcpodyaml/delete_cpcpodyaml.dspy b/wwwroot/cpcpodyaml/delete_cpcpodyaml.dspy new file mode 100644 index 0000000..80fc916 --- /dev/null +++ b/wwwroot/cpcpodyaml/delete_cpcpodyaml.dspy @@ -0,0 +1,138 @@ + +ns = { + 'id':params_kw['id'], +} + +yamlconfig_id = params_kw.get("id") + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + + # 成功完成k8s资源级联删除后才可以删除数据库记录 + yaml_configs = await sor.R('yaml_config', {'id':yamlconfig_id}) + if len(yaml_configs) < 1: + e = Exception(f'yaml_config {yamlconfig_id=} not exists') + exception(f'{e}') + raise e + yaml_config = yaml_configs[0] + + source_podengine = yaml_config.get("source_podengine") + clusterid = yaml_config.get("clusterid") + cpcid = yaml_config.get("cpcid") + debug(f'===== delete yamlconfig_id: {yamlconfig_id}') + + cpclusters = await sor.R('cpccluster', {'id':clusterid}) + if len(cpclusters) < 1: + e = Exception(f'cpclist {clusterid=} not exists') + exception(f'{e}') + raise e + cpcluster = cpclusters[0] + # 此处的nodeid即控制节点id + kubeconfig = cpcluster.get("kubeconfig") + nodeid = cpcluster.get("controllerid") + + cpcs = await sor.R('cpclist', {'id':cpcid}) + if len(cpcs) < 1: + e = Exception(f'cpclist {cpcid=} not exists') + exception(f'{e}') + raise e + cpc = cpcs[0] + nodes = await sor.R('cpcnode', {'id':nodeid}) + # 这里有问题:没有匹配到节点信息!!! + if len(nodes) < 1: + e = Exception(f'cpcnode {nodeid=} 节点基础信息不匹配') + exception(f'{e}') + raise e + node = nodes[0] + url = cpc.pcapi_url + "/pcapi/api/v1/cluster/common/delete_cpcpod" + debug(f"请求url: {url=}") + debug(f"目标IP认证信息: {node.ip=} {node.sshport=} {node.adminuser=} {password_decode(node.adminpwd)=}") + # 请求方式待定,取决于获取参数值方式 + + userorgid = await get_userorgid() + debug(f'当前组织ID:{userorgid}') + if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } + + headers = basic_auth_headers(cpc.api_user, password_decode(cpc.api_pwd)) + ns_name = clusterid.replace("_","-") + "-" + userorgid # 集群信息+组织信息合成命名空间 + ns_name = ns_name.lower() + hc = HttpClient() + + # type参数:更新和新增都是为apply,删除为delete + yamlconfig_id = params_kw.get('id') + + import requests + params = { + 'host':node.ip, + 'port':node.sshport, + 'user':node.adminuser, + 'psssword':password_decode(node.adminpwd), + 'kubeconfig':kubeconfig, + 'action':'delete', + 'namespace_name': ns_name, + 'serviceaccount_name': ns_name + "-serviceaccount", + 'podcd_name': yamlconfig_id + "-" + source_podengine.lower(), + 'service_name': yamlconfig_id + "-service", #取代+ "-" + source_name + } + params.update(yaml_config) + + keys_to_remove = ['_webbricks_', 'width', 'height', '_is_mobile'] + for key in keys_to_remove: + if key in params: + del params[key] + + debug(f'请求pcapi参数: {params}') + + # 框架不支持超时时间 + resp = requests.post(url, + headers = headers, + data = params, + timeout = 500 + ) + + debug(f'{type(resp)}->{resp}') + resp = json.dumps(resp.json()) #这里模拟hc.request返回的结果写后续逻辑 + data = json.loads(resp).get('data') + keys_to_remove = ['host', 'port', 'user', 'psssword', 'kubeconfig'] + for key in keys_to_remove: + if key in params: + del params[key] + params['id'] = yamlconfig_id + + if json.loads(resp).get("status") == True: + + r = await sor.D('yaml_config', ns) + debug('级联删除集群资源成功'); + return { + "widgettype":"Message", + "options":{ + "title":"级联删除集群资源成功", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"ok" + } + } + +debug('级联删除集群资源失败'); +return { + "widgettype":"Error", + "options":{ + "title":"级联删除集群资源失败", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/cpcpodyaml/get_cpcpodyaml.dspy b/wwwroot/cpcpodyaml/get_cpcpodyaml.dspy new file mode 100644 index 0000000..685ebce --- /dev/null +++ b/wwwroot/cpcpodyaml/get_cpcpodyaml.dspy @@ -0,0 +1,121 @@ + +ns = params_kw.copy() + + +debug(f'get_cpcnode.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + ns['sort'] = 'id' + + +sql = '''select * from yaml_config''' + + +filterjson = params_kw.get('data_filter') +if not filterjson: + fields = [ f['name'] for f in [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 255 + }, + { + "name": "devicetype", + "title": "设备类型", + "type": "str", + "length": 1 + }, + { + "name": "ip", + "title": "内网ip", + "type": "str", + "length": 90 + }, + { + "name": "sshport", + "title": "ssh端口号", + "type": "short" + }, + { + "name": "adminuser", + "title": "管理账号", + "type": "str", + "length": 99 + }, + { + "name": "adminpwd", + "title": "管理密码", + "type": "str", + "length": 99 + }, + { + "name": "cpcid", + "title": "算力中心id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "node_status", + "title": "节点状态", + "type": "str", + "length": 1, + "nullable": "yes" + }, + { + "name": "clusterid", + "title": "所属集群", + "type": "long", + "length": 32, + "nullable": "yes" + }, + { + "name": "enable_date", + "title": "启用日期", + "type": "date", + "length": 255 + }, + { + "name": "export_date", + "title": "停用日期", + "type": "date", + "length": 255 + } +] ] + 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 = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/cpcpodyaml/index.ui b/wwwroot/cpcpodyaml/index.ui new file mode 100644 index 0000000..b624e57 --- /dev/null +++ b/wwwroot/cpcpodyaml/index.ui @@ -0,0 +1,278 @@ +{ + "id": "cpcnode_tbl", + "widgettype": "Tabular", + "options": { + "title": "集群资源实例YAML配置", + "description":"显示非系统命名空间下所有资源实例对应的静态YAML参数,更新资源YAML后,kubernetes集群会根据整体资源情况,自动调整资源实例", + "css": "card", + "editable": { + "new_data_url": "{{entire_url('add_cpcpodyaml.dspy')}}", + "delete_data_url": "{{entire_url('delete_cpcpodyaml.dspy')}}", + "update_data_url": "{{entire_url('update_cpcpodyaml.dspy')}}" + }, + "data_url": "{{entire_url('./get_cpcpodyaml.dspy')}}", + "data_method": "GET", + "data_params": {{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options": { + "browserfields": { + "exclouded": [ + "cpcid", + "clusterid" + ], + "alters": {} + }, + "editexclouded": [ + "id", + "cpcid", + "clusterid" + ], + "fields": [ + { + "name": "id", + "title": "资源级联标识", + "type": "str", + "length": 32, + "cwidth": 12, + "uitype": "str", + "datatype": "str", + "label": "资源级联标识" + }, + { + "name": "cpcid", + "title": "算力中心id", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "算力中心id" + }, + { + "name": "clusterid", + "title": "所属集群", + "type": "long", + "length": 0, + "nullable": "yes", + "cwidth": 18, + "uitype": "int", + "datatype": "long", + "label": "所属集群" + }, + { + "name": "source_name", + "title": "资源名称", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "资源名称" + }, + { + "name": "namespace_name", + "title": "命名空间", + "type": "str", + "length": 255, + "cwidth": 14, + "uitype": "str", + "datatype": "str", + "label": "命名空间" + }, + { + "name": "serviceaccount_name", + "title": "服务认证", + "type": "str", + "length": 255, + "cwidth": 16, + "uitype": "str", + "datatype": "str", + "label": "服务认证" + }, + { + "name": "podcd_name", + "title": "资源控制器名称", + "type": "str", + "length": 255, + "cwidth": 16, + "uitype": "str", + "datatype": "str", + "label": "资源控制器名称" + }, + { + "name": "instance_type", + "title": "资源实例类型", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "资源实例类型" + }, + { + "name": "service_name", + "title": "服务名称", + "type": "str", + "length": 255, + "cwidth": 16, + "uitype": "str", + "datatype": "str", + "label": "服务名称" + }, + { + "name": "source_replicasetnum", + "title": "副本数量", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "副本数量" + }, + { + "name": "source_authuser", + "title": "默认账号", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "默认账号" + }, + { + "name": "source_authpasswd", + "title": "默认密码", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "默认密码" + }, + { + "name": "source_podengine", + "title": "资源控制器", + "type": "str", + "length": 255, + "cwidth": 7, + "uitype": "str", + "datatype": "str", + "label": "资源控制器" + }, + { + "name": "pod_imagepath", + "title": "基础镜像", + "type": "str", + "length": 255, + "cwidth": 16, + "uitype": "str", + "datatype": "str", + "label": "基础镜像" + }, + { + "name": "source_memrate", + "title": "内存限制", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "内存限制" + }, + { + "name": "source_cpurate", + "title": "CPU限制", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "CPU限制" + }, + { + "name": "source_selflabel", + "title": "资源标签", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "资源标签" + }, + { + "name": "source_portmode", + "title": "端口映射模式", + "type": "str", + "length": 255, + "cwidth": 7, + "uitype": "str", + "datatype": "str", + "label": "端口映射模式" + }, + { + "name": "source_restartpolicy", + "title": "重启策略", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "重启策略" + }, + { + "name": "source_apiport", + "title": "集群内部映射端口", + "type": "str", + "length": 255, + "cwidth": 9, + "uitype": "str", + "datatype": "str", + "label": "集群内部映射端口" + }, + { + "name": "source_insideport", + "title": "容器默认监听端口", + "type": "str", + "length": 255, + "cwidth": 9, + "uitype": "str", + "datatype": "str", + "label": "容器默认监听端口" + }, + { + "name": "source_outsideport", + "title": "集群外部映射端口", + "type": "str", + "length": 255, + "cwidth": 9, + "uitype": "str", + "datatype": "str", + "label": "集群外部映射端口" + }, + { + "name": "source_mountpath", + "title": "容器内部挂载点", + "type": "str", + "length": 255, + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "容器内部挂载点" + }, + { + "name": "source_storagelimits", + "title": "容器存储盘限制", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "容器存储盘限制" + } + ] + }, + "page_rows": 160, + "cache_limit": 5 + }, + "binds": [] +} \ No newline at end of file diff --git a/wwwroot/cpcpodyaml/update_cpcpodyaml.dspy b/wwwroot/cpcpodyaml/update_cpcpodyaml.dspy new file mode 100644 index 0000000..bf53604 --- /dev/null +++ b/wwwroot/cpcpodyaml/update_cpcpodyaml.dspy @@ -0,0 +1,110 @@ + +ns = params_kw.copy() +# cpcid +# clusterid + +if params_kw.get('adminpwd'): + ns['adminpwd'] = password_encode(params_kw.get('adminpwd')) + +clusterid = params_kw.get("clusterid") +cpcid = params_kw.get("cpcid") + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + + # 补充['cluster_type', 'host', 'port', 'user', 'psssword', 'kubeconfig'] + + cpclusters = await sor.R('cpccluster', {'id':clusterid}) + if len(cpclusters) < 1: + e = Exception(f'cpclist {clusterid=} not exists') + exception(f'{e}') + raise e + cpcluster = cpclusters[0] + # 此处的nodeid即控制节点id + kubeconfig = cpcluster.get("kubeconfig") + nodeid = cpcluster.get("controllerid") + + cpcs = await sor.R('cpclist', {'id':cpcid}) + if len(cpcs) < 1: + e = Exception(f'cpclist {cpcid=} not exists') + exception(f'{e}') + raise e + cpc = cpcs[0] + nodes = await sor.R('cpcnode', {'id':nodeid}) + # 这里有问题:没有匹配到节点信息!!! + if len(nodes) < 1: + e = Exception(f'cpcnode {nodeid=} 节点基础信息不匹配') + exception(f'{e}') + raise e + node = nodes[0] + url = cpc.pcapi_url + "/pcapi/api/v1/cluster/common/update_cpcpod" + debug(f"请求url: {url=}") + debug(f"目标IP认证信息: {node.ip=} {node.sshport=} {node.adminuser=} {password_decode(node.adminpwd)=}") + # 请求方式待定,取决于获取参数值方式 + + userorgid = await get_userorgid() + debug(f'当前组织ID:{userorgid}') + if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } + + headers = basic_auth_headers(cpc.api_user, password_decode(cpc.api_pwd)) + ns_name = clusterid.replace("_","-") + "-" + userorgid # 集群信息+组织信息合成命名空间 + ns_name = ns_name.lower() + hc = HttpClient() + # type参数:更新和新增都是为apply,删除为delete + yamlconfig_id = params_kw.get('id') + import requests + params = { + 'cluster_type': cluster_type, + 'host':node.ip, + 'port':node.sshport, + 'user':node.adminuser, + 'psssword':password_decode(node.adminpwd), + 'kubeconfig':kubeconfig, + 'action':'apply', + 'namespace_name': ns_name, + 'serviceaccount_name': ns_name + "-serviceaccount", + 'podcd_name': yamlconfig_id + "-" + params_kw.get("source_podengine").lower(), + 'service_name': yamlconfig_id + "-service", #取代+ "-" + params_kw.get("source_name") + } + params.update(params_kw) + + keys_to_remove = ['_webbricks_', 'width', 'height', '_is_mobile'] + for key in keys_to_remove: + if key in params: + del params[key] + + debug(f'请求pcapi参数: {params=}') + # 框架不支持超时时间 + resp = requests.post(url, + headers = headers, + data=params, + timeout=500 + ) + resp = json.dumps(resp.json()) #这里模拟hc.request返回的结果写后续逻辑 + debug(f'{type(resp)=}->{resp=}') + data = json.loads(resp).get('data') + keys_to_remove = ['cluster_type', 'host', 'port', 'user', 'psssword', 'kubeconfig'] + for key in keys_to_remove: + if key in params: + del params[key] + params['id'] = yamlconfig_id + if json.loads(resp).get("status") == True: + + r = await sor.U('yaml_config', ns) + debug('update success'); + return UiMessage(title='更新YAML参数', message='资源参数更新成功,请10秒后查看实时资源实例面板') + else: + return UiError(title='更新YAML参数', message='资源实例更新失败') + +return UiError(title='更新YAML参数', message='其他错误') \ No newline at end of file diff --git a/wwwroot/cpcworker/add_cpcworker.dspy b/wwwroot/cpcworker/add_cpcworker.dspy new file mode 100644 index 0000000..361a2ba --- /dev/null +++ b/wwwroot/cpcworker/add_cpcworker.dspy @@ -0,0 +1,35 @@ + +ns = params_kw.copy() +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('cpccluster', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpcworker/delete_cpcworker.dspy b/wwwroot/cpcworker/delete_cpcworker.dspy new file mode 100644 index 0000000..3d40fe8 --- /dev/null +++ b/wwwroot/cpcworker/delete_cpcworker.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('cpccluster', 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" + } +} \ No newline at end of file diff --git a/wwwroot/cpcworker/get_availableworker.dspy b/wwwroot/cpcworker/get_availableworker.dspy new file mode 100644 index 0000000..baca0d7 --- /dev/null +++ b/wwwroot/cpcworker/get_availableworker.dspy @@ -0,0 +1,11 @@ +db = DBPools() +dbname = get_module_dbname('cpcc') +ns = { + "clusterid":params_kw.clusterid +} +async with db.sqlorContext(dbname) as sor: + sql = "select cpcid,id,ip,node_status from cpcnode where cpcid = (select cpcid from cpccluster where id = ${clusterid}$) and node_status = '0';" + recs = await sor.sqlExe(sql, ns.copy()) + return recs +exception(f'{sql=},{ns=}') +return [] \ No newline at end of file diff --git a/wwwroot/cpcworker/get_cpcworker.dspy b/wwwroot/cpcworker/get_cpcworker.dspy new file mode 100644 index 0000000..698c08f --- /dev/null +++ b/wwwroot/cpcworker/get_cpcworker.dspy @@ -0,0 +1,84 @@ +ns = params_kw.copy() + +debug(f'get_cpcworker.dspy:{ns=}') +#{'_webbricks_': '1', 'width': '1139', 'height': '940', '_is_mobile': '0', +#'clusterid': '4hBm8atruISOU2bs24t_N', 'cpcid': 'AROU9udKtPNyh0AZtO_WY', 'page': '1', 'rows': '160'} + +cpcid = params_kw.cpcid +clusterid = params_kw.clusterid + +# 写数据库的步骤先pass +dbname = get_module_dbname('cpcc') +db = DBPools() + +async with db.sqlorContext(dbname) as sor: + # 通过算力中心ID,获取算力中心部署所在设备的http(s)协议信息 + cpcs = await sor.R('cpclist', {'id':cpcid}) + if len(cpcs) < 1: + e = Exception(f'cpclist {cpcid=} not exists') + exception(f'{e}') + raise e + cpc = cpcs[0] + # 通过集群ID,获取算力集群控制节点所在设备的ssh协议参数 + cpclusters = await sor.R('cpccluster', {'id':clusterid}) + if len(cpclusters) < 1: + e = Exception(f'cpclist {clusterid=} not exists') + exception(f'{e}') + raise e + cpcluster = cpclusters[0] + nodeid = cpcluster.get("controllerid") + kubeconfig = cpcluster.get("kubeconfig") + nodes = await sor.R('cpcnode', {'id':nodeid}) + # 这里有问题:没有匹配到节点信息!!! + if len(nodes) < 1: + e = Exception(f'cpcnode {nodeid=} 节点基础信息不匹配') + exception(f'{e}') + raise e + node = nodes[0] + url = cpc.pcapi_url + "/pcapi/api/v1/cluster/common/get_cluster_nodes" + debug(f"请求url: {url=}") + debug(f"目标IP认证信息: {node.ip=} {node.sshport=} {node.adminuser=} {password_decode(node.adminpwd)=}") + # 请求方式待定,取决于获取参数值方式 + + headers = basic_auth_headers(cpc.api_user, password_decode(cpc.api_pwd)) + + hc = HttpClient() + + import requests + params = { + 'host':node.ip, + 'port':node.sshport, + 'user':node.adminuser, + 'psssword':password_decode(node.adminpwd), + 'kubeconfig':kubeconfig + } + debug(f'请求参数{params=}') + + #resp = await hc.request(url, method='GET', + # headers = headers, + # data=params + #) + # 框架不支持超时时间 + resp = requests.post(url, + headers = headers, + data=params, + timeout=500 + ) + resp = json.dumps(resp.json()) #这里模拟hc.request返回的结果写后续逻辑 + #debug(f'{type(resp)=}->{resp=}') + debug(f"pcapi返回值: {json.loads(resp)=}") + data = json.loads(resp).get('data') + if json.loads(resp).get("status") == True: + #node_ns = { + # 'id':nodeid, + # 'node_status':'1', + # 'clusterid':clusterid, + # 'cpcid':cpcid + #} + #debug(f"更新新增工作节点元数据 {node_ns=}") + #await sor.U('cpcnode', node_ns) + return data + else: + return UiError(title='get cluster nodes', message='failed') + +return UiError(title='get cluster nodes', message='uncatched error') \ No newline at end of file diff --git a/wwwroot/cpcworker/index.ui b/wwwroot/cpcworker/index.ui new file mode 100644 index 0000000..e9f47c5 --- /dev/null +++ b/wwwroot/cpcworker/index.ui @@ -0,0 +1,179 @@ +{ + "id": "cpcworker_tbl", + "widgettype": "Tabular", + "options": { + "title": "集群实时节点", + "description":"显示集群中所有实时节点信息", + "css": "card", + "editable": { + "new_data_url": "{{entire_url('add_cpcworker.dspy')}}", + "delete_data_url": "{{entire_url('delete_cpcworker.dspy')}}", + "update_data_url": "{{entire_url('update_cpcworker.dspy')}}" + }, + "data_url": "{{entire_url('./get_cpcworker.dspy')}}", + "data_method": "GET", + "data_params": {{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options": { + "browserfields": { + "exclouded": [ + "id", + "cpcid", + "clusterid" + ], + "alters": {} + }, + "editexclouded": [ + "id", + "cpcid", + "clusterid" + ], + "fields": [ + { + "name": "node_internalip", + "title": "内网IP", + "type": "str", + "length": 255, + "cwidth": 6, + "uitype": "str", + "datatype": "str", + "label": "内网IP" + }, + { + "name": "node_name", + "title": "主机名", + "type": "str", + "length": 255, + "cwidth": 15, + "uitype": "str", + "datatype": "str", + "label": "主机名" + }, + { + "name": "node_status", + "title": "运行状态", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "运行状态" + }, + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "cpcid", + "title": "算力中心id", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "算力中心id" + }, + { + "name": "node_role", + "title": "节点角色", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "节点角色" + }, + { + "name": "node_age", + "title": "运行时长", + "type": "str", + "length": 255, + "cwidth": 5, + "uitype": "str", + "datatype": "str", + "label": "运行时长" + }, + { + "name": "node_version", + "title": "kubernetes版本", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "kubernetes版本" + }, + { + "name": "node_cpurate", + "title": "CPU占用率", + "type": "str", + "length": 255, + "cwidth": 6, + "uitype": "str", + "datatype": "str", + "label": "CPU占用率" + }, + { + "name": "node_memrate", + "title": "内存占用率", + "type": "str", + "length": 255, + "cwidth": 6, + "uitype": "str", + "datatype": "str", + "label": "内存占用率" + }, + { + "name": "node_osversion", + "title": "操作系统", + "type": "str", + "length": 255, + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "操作系统" + }, + { + "name": "node_containeruntime", + "title": "容器运行时版本", + "type": "str", + "length": 255, + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "容器运行时" + }, + { + "name": "node_kernelversion", + "title": "内核版本", + "type": "str", + "length": 255, + "cwidth": 10, + "uitype": "str", + "datatype": "str", + "label": "内核版本" + }, + { + "name": "node_externalip", + "title": "外网IP", + "type": "str", + "length": 255, + "cwidth": 8, + "uitype": "str", + "datatype": "str", + "label": "外网IP" + } + ] + }, + "page_rows": 160, + "cache_limit": 5 + }, + "binds": [] +} \ No newline at end of file diff --git a/wwwroot/cpcworker/new_cpcworker.dspy b/wwwroot/cpcworker/new_cpcworker.dspy new file mode 100644 index 0000000..35f3aaa --- /dev/null +++ b/wwwroot/cpcworker/new_cpcworker.dspy @@ -0,0 +1,83 @@ +# params_kw: +# clusterid +# worker_nodeid +debug(f'{params_kw=}') +dbname = get_module_dbname('cpcc') +db = DBPools() +clusterid = params_kw.clusterid +nodeid = params_kw.worker_nodeid +#debug(f"=====> {clusterid=} {worker_nodeid=}") + +async with db.sqlorContext(dbname) as sor: + # clusterid -> cpcid + cpclusters = await sor.R('cpccluster', {'id':clusterid}) + if len(cpclusters) < 1: + e = Exception(f'cpclist {clusterid=} not exists') + exception(f'{e}') + raise e + cpcluster = cpclusters[0] + join_command = cpcluster.get("clusterjoin") + cpcid = cpcluster.get("cpcid") + cpcs = await sor.R('cpclist', {'id':cpcid}) + if len(cpcs) < 1: + e = Exception(f'cpclist {cpcid=} not exists') + exception(f'{e}') + raise e + cpc = cpcs[0] + debug(f'集群注册命令:{join_command=}') + nodes = await sor.R('cpcnode', {'id':nodeid}) + # 这里有问题:没有匹配到节点信息!!! + if len(nodes) < 1: + e = Exception(f'cpcnode {nodeid=} 节点基础信息不匹配') + exception(f'{e}') + raise e + node = nodes[0] + url = cpc.pcapi_url + "/pcapi/api/v1/cluster/common/new_worker" + debug(f"请求url: {url=}") + debug(f"目标IP认证信息: {node.ip=} {node.sshport=} {node.adminuser=} {password_decode(node.adminpwd)=}") + # 请求方式待定,取决于获取参数值方式 + + headers = basic_auth_headers(cpc.api_user, password_decode(cpc.api_pwd)) + + hc = HttpClient() + + import requests + params = { + 'cluster_type': cluster_type, + 'host':node.ip, + 'port':node.sshport, + 'user':node.adminuser, + 'psssword':password_decode(node.adminpwd), + 'role':"worker", + 'join_command':join_command + } + debug(f'{params=}') + + #resp = await hc.request(url, method='POST', + # headers = headers, + # data=params + #) + # 框架不支持超时时间 + resp = requests.post(url, + headers = headers, + data=params, + timeout=500 + ) + resp = json.dumps(resp.json()) #这里模拟hc.request返回的结果写后续逻辑 + debug(f'{type(resp)=}->{resp=}') + debug(f"pcapi返回值: {json.loads(resp)=}") + data = json.loads(resp).get('data') + if json.loads(resp).get("status") == True: + node_ns = { + 'id':nodeid, + 'node_status':'1', + 'clusterid':clusterid, + 'cpcid':cpcid + } + debug(f"更新新增工作节点元数据 {node_ns=}") + await sor.U('cpcnode', node_ns) + return UiMessage(title='new cluster', message='操作成功!') + else: + return UiError(title='new cluster', message='failed') + +return UiError(title='new cluster', message='uncatched error') diff --git a/wwwroot/cpcworker/new_worker.ui b/wwwroot/cpcworker/new_worker.ui new file mode 100644 index 0000000..20f978c --- /dev/null +++ b/wwwroot/cpcworker/new_worker.ui @@ -0,0 +1,33 @@ +{ + "widgettype":"Form", + "options":{ + "title":"新增工作节点", + "description":"为算力中心的kubernetes集群新增工作节点,目前支持kubernetes工作模式", + "fields":[ + { + "name":"clusterid", + "value":"{{params_kw.id}}", + "uitype":"hide" + }, + { + "name":"worker_nodeid", + "label":"请选择安装工作模式的节点", + "uitype":"code", + "textField":"ip", + "valueField":"id", + "dataurl":"{{entire_url('get_availableworker.dspy')}}?clusterid={{params_kw.id}}" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"PopupWindow", + "options":{ + "url":"{{entire_url('new_cpcworker.dspy')}}" + } + } + ] +} diff --git a/wwwroot/cpcworker/update_cpcworker.dspy b/wwwroot/cpcworker/update_cpcworker.dspy new file mode 100644 index 0000000..af390dd --- /dev/null +++ b/wwwroot/cpcworker/update_cpcworker.dspy @@ -0,0 +1,32 @@ + +ns = params_kw.copy() + + + + +db = DBPools() +dbname = await rfexe('get_module_dbname', 'cpcc') +async with db.sqlorContext(dbname) as sor: + r = await sor.U('cpccluster', 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" + } +} \ No newline at end of file diff --git a/wwwroot/handy/get_cpcnodes.dspy b/wwwroot/handy/get_cpcnodes.dspy new file mode 100644 index 0000000..67a740e --- /dev/null +++ b/wwwroot/handy/get_cpcnodes.dspy @@ -0,0 +1,12 @@ +debug(f'{params_kw=}') +db = DBPools() +dbname = get_module_dbname('cpcc') +ns = { + "cpcid":params_kw.cpcid +} +async with db.sqlorContext(dbname) as sor: + sql = "select * from cpcnode where cpcid=${cpcid}$ and node_status = '0'" + recs = await sor.sqlExe(sql, ns.copy()) + return recs +exception(f'{sql=},{ns=}') +return [] diff --git a/wwwroot/handy/new_cluster.dspy b/wwwroot/handy/new_cluster.dspy new file mode 100644 index 0000000..2307dfe --- /dev/null +++ b/wwwroot/handy/new_cluster.dspy @@ -0,0 +1,97 @@ +# params_kw: +# cpcid +# cluster_type +# ctl_nodeid +# cluster_name +debug(f'{params_kw=}') +dbname = get_module_dbname('cpcc') +db = DBPools() +cpcid = params_kw.cpcid +nodeid = params_kw.ctl_nodeid +cluster_type = params_kw.cluster_type +cluster_name = params_kw.cluster_name +enable_date = params_kw.enable_date +export_date = params_kw.export_date +# debug(f"=====> {cluster_type=} {cluster_name=} {nodeid=} {cpcid=}") + +async with db.sqlorContext(dbname) as sor: + cpcs = await sor.R('cpclist', {'id':cpcid}) + if len(cpcs) < 1: + e = Exception(f'cpclist {cpcid=} not exists') + exception(f'{e}') + raise e + cpc = cpcs[0] + nodes = await sor.R('cpcnode', {'id':nodeid}) + # 这里有问题:没有匹配到节点信息!!! + if len(nodes) < 1: + e = Exception(f'cpcnode {nodeid=} 节点基础信息不匹配') + exception(f'{e}') + raise e + node = nodes[0] + url = cpc.pcapi_url + "/pcapi/api/v1/cluster/common/new_cluster" + debug(f"请求url: {url=}") + debug(f"目标IP认证信息: {node.ip=} {node.sshport=} {node.adminuser=} {password_decode(node.adminpwd)=}") + # 请求方式待定,取决于获取参数值方式 + debug(333) + headers = basic_auth_headers('ysh', 'Kyy@123456') + debug(22) + hc = HttpClient() + print(444) + import requests + params = { + 'cluster_type': cluster_type, + 'host':node.ip, + 'port':node.sshport, + 'user':node.adminuser, + 'psssword':password_decode(node.adminpwd), + 'role':"master" + } + debug(f'{params=}') + debug(1111111) + #resp = await hc.request(url, method='POST', + # headers = headers, + # data=params + #) + # 框架不支持超时时间 + resp = requests.post(url, + headers = headers, + data=params, + timeout=500 + ) + resp = json.dumps(resp.json()) #这里模拟hc.request返回的结果写后续逻辑 + debug(f'{type(resp)=}->{resp=}') + debug(f"pcapi返回值: {json.loads(resp)=}") + 2datas = json.loads(resp).get('data') + 2datas = 2datas.split("\n") + clusterjoin = 2datas[0] + kubeconfig_context = 2datas[1] + if json.loads(resp).get("status") == True: + # new cluster info write to database + # update node_status to '1' cpcnode record identify by ctl_nodeid ctl_node + clusterid = uuid() + ns = { + 'id':clusterid, + 'clustertype':cluster_type, + 'cpcid':cpcid, + 'controllerid':nodeid, + 'name':cluster_name, + 'enable_date':enable_date, + 'export_date':export_date, + 'clusterjoin':clusterjoin, + 'kubeconfig':kubeconfig_context + } + debug(f"新集群元数据: {ns=}") + await sor.C('cpccluster', ns) + node_ns = { + 'id':nodeid, + 'node_status':'1', + 'clusterid':clusterid, + 'cpcid':cpcid + } + debug(f"更新控制节点元数据 {node_ns=}") + await sor.U('cpcnode', node_ns) + return UiMessage(title='new cluster', message='操作成功!\n加入集群凭证: %s' % clusterjoin) + else: + return UiError(title='new cluster', message='failed') + +return UiError(title='new cluster', message='uncatched error') diff --git a/wwwroot/handy/new_cluster.ui b/wwwroot/handy/new_cluster.ui new file mode 100644 index 0000000..1bbaf78 --- /dev/null +++ b/wwwroot/handy/new_cluster.ui @@ -0,0 +1,79 @@ +{ + "widgettype":"Form", + "options":{ + "title":"创建集群", + "description":"为算力中心创建集群,目前支持kubernetes集群", + "fields":[ + { + "name":"cpcid", + "value":"{{params_kw.id}}", + "uitype":"hide" + }, + { + "name":"cluster_type", + "label":"集群类型", + "data":[ + { + "value":"0", + "text":"kubernetes" + }, + { + "value":"1", + "text":"slurm" + } + ], + "value":"0", + "uitype":"code" + }, + { + "name": "cluster_name", + "title": "集群名称", + "type": "str", + "length": 90, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "集群名称" + }, + { + "name":"ctl_nodeid", + "label":"控制节点IP", + "uitype":"code", + "textField":"ip", + "valueField":"id", + "dataurl":"{{entire_url('get_cpcnodes.dspy')}}?cpcid={{params_kw.id}}" + }, + { + "name": "enable_date", + "title": "启用日期", + "type": "date", + "length": 0, + "cwidth": 18, + "uitype": "date", + "datatype": "date", + "label": "启用日期" + }, + { + "name": "export_date", + "title": "停用日期", + "type": "date", + "length": 0, + "cwidth": 18, + "uitype": "date", + "datatype": "date", + "label": "停用日期" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"PopupWindow", + "options":{ + "url":"{{entire_url('new_cluster.dspy')}}" + } + } + ] +} diff --git a/wwwroot/index.ui b/wwwroot/index.ui new file mode 100644 index 0000000..2cbe938 --- /dev/null +++ b/wwwroot/index.ui @@ -0,0 +1,27 @@ +{ + "widgettype":"VBox", + "options":{ + "width":"100%", + "height":"100%" + }, + "subwidgets":[ + { + "widgettype":"urlwidget", + "options":{ + "url":"{{entire_url('top.ui')}}" + } + }, + { + "widgettype":"urlwidget", + "options":{ + "url":"{{entire_url('center.ui')}}" + } + }, + { + "widgettype":"urlwidget", + "options":{ + "url":"{{entire_url('bottom.ui')}}" + } + } + ] +} diff --git a/wwwroot/menu.ui b/wwwroot/menu.ui new file mode 100644 index 0000000..0ddca6d --- /dev/null +++ b/wwwroot/menu.ui @@ -0,0 +1,20 @@ +{% set roles = get_user_roles(get_user()) %} +{ + "widgettype":"Menu", + "options":{ + "target":"page_center", + "cwidth":10, + "items":[ + { + "name":"home", + "label":"代码管理", + "url":"{{entire_url('/appbase/appcodes')}}" + }, + { + "name":"home", + "label":"算力中心管理", + "url":"{{entire_url('cpclist')}}" + } + ] + } +} diff --git a/wwwroot/top.ui b/wwwroot/top.ui new file mode 100644 index 0000000..2684873 --- /dev/null +++ b/wwwroot/top.ui @@ -0,0 +1,23 @@ +{ + "id":"top_panel", + "widgettype":"HBox", + "options":{ + "bgcolor":"#444444", + "cheight":2.5, + "color":"#eeeeee" + }, + "subwidgets":[ + { + "widgettype":"urlwidget", + "options":{ + "url":"{{entire_url('app_panel.ui')}}" + } + }, + { + "widgettype":"urlwidget", + "options":{ + "url":"{{entire_url('/rbac/user/user_panel.ui')}}" + } + } + ] +}