first commit

This commit is contained in:
yumoqing 2025-07-16 14:32:09 +08:00
commit 57e5c736c0
64 changed files with 4176 additions and 0 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# cpcc
a computing power center manages client

60
app/cpcc.py Normal file
View File

@ -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

81
conf/config.json Executable file
View File

@ -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"
}
}

BIN
docs/cpc数据表.xlsx Normal file

Binary file not shown.

3
json/build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/bash
xls2ui -m ../models -o ../wwwroot cpcc *.json

13
json/components.json Normal file
View File

@ -0,0 +1,13 @@
{
"tblname":"components",
"params":{
"browserfields": {
"exclouded": ["id"],
"alters": {}
},
"editexclouded": [
"id"
]
}
}

13
json/cpccluster.json Normal file
View File

@ -0,0 +1,13 @@
{
"tblname":"cpccluster",
"params":{
"browserfields": {
"exclouded": ["id", "cpcid"],
"alters": {}
},
"editexclouded": [
"id", "cpcid"
]
}
}

58
json/cpclist.json Normal file
View File

@ -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":"节点"
}
]
}
}

16
json/cpcnode.json Normal file
View File

@ -0,0 +1,16 @@
{
"tblname":"cpcnode",
"params":{
"confidential_fields":["adminpwd"],
"browserfields": {
"exclouded": [
"id", "cpcid"
],
"alters": {}
},
"editexclouded": [
"id", "cpcid", "clusterid"
]
}
}

BIN
models/components.xlsx Normal file

Binary file not shown.

BIN
models/cpccluster.xlsx Normal file

Binary file not shown.

BIN
models/cpclist.xlsx Normal file

Binary file not shown.

BIN
models/cpcnode.xlsx Normal file

Binary file not shown.

BIN
models/cpcnode_config.xlsx Normal file

Binary file not shown.

Binary file not shown.

BIN
models/cpvalue.xlsx Normal file

Binary file not shown.

201
models/mysql.ddl.sql Normal file
View File

@ -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 '节点配置明细项'
;

3
script/cpcc_roleperm.sh Normal file
View File

@ -0,0 +1,3 @@
#!/usr/bin/bash
python ~/py/rbac/script/roleperm.py sage cpcc reseller operator cpclist cpcnode cpccluster components

40
wwwroot/app_panel.ui Normal file
View File

@ -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"
}
}
]
}

17
wwwroot/bottom.ui Normal file
View File

@ -0,0 +1,17 @@
{
"widgettype":"HBox",
"options":{
"cheight":2,
"bgcolor":"#e5e5e5"
},
"subwidgets":[
{
"widgettype":"Text",
"options":{
"otext":"© 2024 版权所有, 开元云(北京)科技有限公司",
"i18n":true,
"wrap":true
}
}
]
}

15
wwwroot/center.ui Normal file
View File

@ -0,0 +1,15 @@
{
"widgettype":"VBox",
"id":"page_center",
"options":{
"css":"filler"
},
"subwidgets":[
{
"widgettype":"urlwidget",
"options":{
"url":"{{entire_url('/appbase/appcodes')}}"
}
}
]
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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":[]
}

122
wwwroot/components/index.ui Normal file
View File

@ -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":[]
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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":[]
}

239
wwwroot/cpccluster/index.ui Normal file
View File

@ -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}}"
}
}
}
]
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -0,0 +1,47 @@
ns = {
'id':params_kw['id'],
}
userorgid = await get_userorgid()
if not userorgid:
return {
"widgettype":"Error",
"options":{
"title":"Authorization Error",
"timeout":3,
"cwidth":16,
"cheight":9,
"message":"Please login"
}
}
ns['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"
}
}

View File

@ -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":[]
}

209
wwwroot/cpclist/index.ui Normal file
View File

@ -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}}"
}
}
}
]
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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":[]
}

179
wwwroot/cpcnode/index.ui Normal file
View File

@ -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": []
}

View File

@ -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"
}
}

View File

@ -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')

View File

@ -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 []

179
wwwroot/cpcpod/index.ui Normal file
View File

@ -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": []
}

View File

@ -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='全局未知异常')

View File

@ -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')}}"
}
}
]
}

View File

@ -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"
}
}

View File

@ -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":[]
}

278
wwwroot/cpcpodyaml/index.ui Normal file
View File

@ -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": []
}

View File

@ -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='其他错误')

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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 []

View File

@ -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')

179
wwwroot/cpcworker/index.ui Normal file
View File

@ -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": []
}

View File

@ -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')

View File

@ -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')}}"
}
}
]
}

View File

@ -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"
}
}

View File

@ -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 []

View File

@ -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')

View File

@ -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')}}"
}
}
]
}

27
wwwroot/index.ui Normal file
View File

@ -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')}}"
}
}
]
}

20
wwwroot/menu.ui Normal file
View File

@ -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')}}"
}
]
}
}

23
wwwroot/top.ui Normal file
View File

@ -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')}}"
}
}
]
}