From 0bc74fdd1a26fb4e9b71864767997bcaaf38329d Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 26 Jun 2026 16:44:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8C=BA=E7=8A=B6=E6=80=81=E5=B9=B6=E6=8F=90=E4=BA=A4CRUD?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复文件权限 (600→644) - 删除调试日志 (global_func.py) - 添加 CRUD 生成的 wwwroot 文件 (pipeline_core/dist/ops) - 添加 mysql.ddl.sql 文件 - 更新 pyproject.toml 配置 - 添加 showcase 模块安装 (build.sh) - 更新 index.ui (sidebar toggle + 管理按钮) - 设置 start.sh/stop.sh 可执行权限 - 添加 wwwroot 符号链接 (appbase/rbac/pipeline_sdlc) - 更新 .gitignore (排除 pkgs/bricks/pipeline.pid) --- .gitignore | 3 + build.sh | 7 + pipeline_core/mysql.ddl.sql | 115 ++++++ pipeline_core/pyproject.toml | 11 +- .../pipeline_steps/add_pipeline_steps.dspy | 37 ++ .../pipeline_steps/delete_pipeline_steps.dspy | 33 ++ .../pipeline_steps/get_pipeline_steps.dspy | 130 +++++++ pipeline_core/wwwroot/pipeline_steps/index.ui | 241 +++++++++++++ .../pipeline_steps/update_pipeline_steps.dspy | 36 ++ .../add_pipeline_versions.dspy | 37 ++ .../delete_pipeline_versions.dspy | 33 ++ .../get_pipeline_versions.dspy | 113 ++++++ .../wwwroot/pipeline_versions/index.ui | 210 +++++++++++ .../update_pipeline_versions.dspy | 36 ++ .../wwwroot/pipelines/add_pipelines.dspy | 54 +++ .../wwwroot/pipelines/delete_pipelines.dspy | 47 +++ .../wwwroot/pipelines/get_pipelines.dspy | 165 +++++++++ pipeline_core/wwwroot/pipelines/index.ui | 228 ++++++++++++ .../wwwroot/pipelines/update_pipelines.dspy | 73 ++++ pipeline_dist/mysql.ddl.sql | 83 +++++ pipeline_dist/pyproject.toml | 12 +- .../add_distributor_pipeline.dspy | 37 ++ .../delete_distributor_pipeline.dspy | 33 ++ .../get_distributor_pipeline.dspy | 143 ++++++++ .../wwwroot/distributor_pipeline/index.ui | 327 ++++++++++++++++++ .../update_distributor_pipeline.dspy | 36 ++ .../distributors/add_distributors.dspy | 40 +++ .../distributors/delete_distributors.dspy | 33 ++ .../distributors/get_distributors.dspy | 148 ++++++++ pipeline_dist/wwwroot/distributors/index.ui | 240 +++++++++++++ .../distributors/update_distributors.dspy | 39 +++ pipeline_ops/mysql.ddl.sql | 116 +++++++ pipeline_ops/pyproject.toml | 15 +- .../add_pipeline_capacity.dspy | 51 +++ .../delete_pipeline_capacity.dspy | 47 +++ .../get_pipeline_capacity.dspy | 142 ++++++++ .../wwwroot/pipeline_capacity/index.ui | 227 ++++++++++++ .../update_pipeline_capacity.dspy | 70 ++++ .../add_pipeline_pricing.dspy | 51 +++ .../delete_pipeline_pricing.dspy | 47 +++ .../get_pipeline_pricing.dspy | 168 +++++++++ .../wwwroot/pipeline_pricing/index.ui | 322 +++++++++++++++++ .../update_pipeline_pricing.dspy | 70 ++++ .../add_pipeline_usage_log.dspy | 37 ++ .../delete_pipeline_usage_log.dspy | 33 ++ .../get_pipeline_usage_log.dspy | 127 +++++++ .../wwwroot/pipeline_usage_log/index.ui | 265 ++++++++++++++ .../update_pipeline_usage_log.dspy | 36 ++ start.sh | 0 stop.sh | 0 wwwroot/appbase | 1 + wwwroot/index.ui | 206 +++++------ wwwroot/pipeline_sdlc | 1 + wwwroot/rbac | 1 + 54 files changed, 4669 insertions(+), 144 deletions(-) create mode 100644 pipeline_core/mysql.ddl.sql create mode 100644 pipeline_core/wwwroot/pipeline_steps/add_pipeline_steps.dspy create mode 100644 pipeline_core/wwwroot/pipeline_steps/delete_pipeline_steps.dspy create mode 100644 pipeline_core/wwwroot/pipeline_steps/get_pipeline_steps.dspy create mode 100644 pipeline_core/wwwroot/pipeline_steps/index.ui create mode 100644 pipeline_core/wwwroot/pipeline_steps/update_pipeline_steps.dspy create mode 100644 pipeline_core/wwwroot/pipeline_versions/add_pipeline_versions.dspy create mode 100644 pipeline_core/wwwroot/pipeline_versions/delete_pipeline_versions.dspy create mode 100644 pipeline_core/wwwroot/pipeline_versions/get_pipeline_versions.dspy create mode 100644 pipeline_core/wwwroot/pipeline_versions/index.ui create mode 100644 pipeline_core/wwwroot/pipeline_versions/update_pipeline_versions.dspy create mode 100644 pipeline_core/wwwroot/pipelines/add_pipelines.dspy create mode 100644 pipeline_core/wwwroot/pipelines/delete_pipelines.dspy create mode 100644 pipeline_core/wwwroot/pipelines/get_pipelines.dspy create mode 100644 pipeline_core/wwwroot/pipelines/index.ui create mode 100644 pipeline_core/wwwroot/pipelines/update_pipelines.dspy create mode 100644 pipeline_dist/mysql.ddl.sql create mode 100644 pipeline_dist/wwwroot/distributor_pipeline/add_distributor_pipeline.dspy create mode 100644 pipeline_dist/wwwroot/distributor_pipeline/delete_distributor_pipeline.dspy create mode 100644 pipeline_dist/wwwroot/distributor_pipeline/get_distributor_pipeline.dspy create mode 100644 pipeline_dist/wwwroot/distributor_pipeline/index.ui create mode 100644 pipeline_dist/wwwroot/distributor_pipeline/update_distributor_pipeline.dspy create mode 100644 pipeline_dist/wwwroot/distributors/add_distributors.dspy create mode 100644 pipeline_dist/wwwroot/distributors/delete_distributors.dspy create mode 100644 pipeline_dist/wwwroot/distributors/get_distributors.dspy create mode 100644 pipeline_dist/wwwroot/distributors/index.ui create mode 100644 pipeline_dist/wwwroot/distributors/update_distributors.dspy create mode 100644 pipeline_ops/mysql.ddl.sql create mode 100644 pipeline_ops/wwwroot/pipeline_capacity/add_pipeline_capacity.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_capacity/delete_pipeline_capacity.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_capacity/get_pipeline_capacity.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_capacity/index.ui create mode 100644 pipeline_ops/wwwroot/pipeline_capacity/update_pipeline_capacity.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_pricing/add_pipeline_pricing.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_pricing/delete_pipeline_pricing.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_pricing/get_pipeline_pricing.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_pricing/index.ui create mode 100644 pipeline_ops/wwwroot/pipeline_pricing/update_pipeline_pricing.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_usage_log/add_pipeline_usage_log.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_usage_log/delete_pipeline_usage_log.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_usage_log/get_pipeline_usage_log.dspy create mode 100644 pipeline_ops/wwwroot/pipeline_usage_log/index.ui create mode 100644 pipeline_ops/wwwroot/pipeline_usage_log/update_pipeline_usage_log.dspy mode change 100644 => 100755 start.sh mode change 100644 => 100755 stop.sh create mode 120000 wwwroot/appbase create mode 120000 wwwroot/pipeline_sdlc create mode 120000 wwwroot/rbac diff --git a/.gitignore b/.gitignore index ed48494..e085d23 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ logs/ files/ py3/ conf/config.json +pipeline.pid +pkgs/ +bricks diff --git a/build.sh b/build.sh index bb40ea9..9566845 100644 --- a/build.sh +++ b/build.sh @@ -59,6 +59,13 @@ for mod in pipeline_core pipeline_ops pipeline_dist; do cd "$cdir" done +# 4b. Install showcase module (symlinked from repos) +if [ -d "$cdir/showcase" ] || [ -L "$cdir/showcase" ]; then + echo "install showcase ..." + cd "$cdir" + "$cdir/py3/bin/pip" install -e /home/hermesai/repos/showcase 2>&1 | tail -1 +fi + # 5. Create runtime dirs mkdir -p "$cdir/logs" "$cdir/files" diff --git a/pipeline_core/mysql.ddl.sql b/pipeline_core/mysql.ddl.sql new file mode 100644 index 0000000..5a337b8 --- /dev/null +++ b/pipeline_core/mysql.ddl.sql @@ -0,0 +1,115 @@ + +-- models/pipeline_versions.json + + + + + +-- 建库时请用以下语句,支持emoji字符 +-- CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +drop table if exists pipeline_versions; +CREATE TABLE pipeline_versions +( + + `id` VARCHAR(32) NOT NULL comment 'id', + `pipeline_id` VARCHAR(32) NOT NULL comment '产线ID', + `version` VARCHAR(20) NOT NULL comment '版本号', + `publish_status` VARCHAR(20) NOT NULL DEFAULT 'pending' comment '发布状态', + `published_by` VARCHAR(32) comment '发布人', + `published_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '发布时间', + `changelog` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '变更说明', + `config_snapshot` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '配置快照JSON', + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '创建时间' + + +,primary key(id) + + +) +CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci +engine=innodb +comment '产线发布记录表' +; + +CREATE INDEX pipeline_versions_idx_versions_pipeline ON pipeline_versions(pipeline_id); + +-- models/pipelines.json + + + + + +-- 建库时请用以下语句,支持emoji字符 +-- CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +drop table if exists pipelines; +CREATE TABLE pipelines +( + + `id` VARCHAR(32) NOT NULL comment 'id', + `name` VARCHAR(200) NOT NULL comment '产线名称', + `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '产线描述', + `pipeline_type` VARCHAR(50) NOT NULL comment '产线类型', + `version` VARCHAR(20) DEFAULT '1.0.0' comment '当前版本', + `status` VARCHAR(20) NOT NULL DEFAULT 'draft' comment '状态', + `pipeline_config` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '产线配置JSON', + `model_api_url` VARCHAR(500) comment '模型API地址', + `model_api_key` VARCHAR(500) comment '模型API密钥', + `org_id` VARCHAR(32) DEFAULT '0' comment '所属机构ID', + `created_by` VARCHAR(32) comment '创建人', + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '创建时间', + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '更新时间' + + +,primary key(id) + + +) +CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci +engine=innodb +comment '产线定义表' +; + +CREATE INDEX pipelines_idx_pipelines_name ON pipelines(name); +CREATE INDEX pipelines_idx_pipelines_status ON pipelines(status); + +-- models/pipeline_steps.json + + + + + +-- 建库时请用以下语句,支持emoji字符 +-- CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +drop table if exists pipeline_steps; +CREATE TABLE pipeline_steps +( + + `id` VARCHAR(32) NOT NULL comment 'id', + `pipeline_id` VARCHAR(32) NOT NULL comment '所属产线', + `step_order` int NOT NULL comment '步骤序号', + `step_name` VARCHAR(100) NOT NULL comment '步骤名称', + `step_type` VARCHAR(50) NOT NULL comment '步骤类型', + `model_name` VARCHAR(100) comment '调用模型名称', + `step_config` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '步骤配置JSON', + `input_schema` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '输入定义JSON', + `output_schema` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '输出定义JSON', + `timeout_seconds` int DEFAULT '300' comment '超时秒数', + `retry_count` int DEFAULT '0' comment '重试次数', + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '创建时间' + + +,primary key(id) + + +) +CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci +engine=innodb +comment '产线步骤表' +; + +CREATE INDEX pipeline_steps_idx_steps_pipeline ON pipeline_steps(pipeline_id); +CREATE UNIQUE INDEX pipeline_steps_idx_steps_order ON pipeline_steps(pipeline_id,step_order); + diff --git a/pipeline_core/pyproject.toml b/pipeline_core/pyproject.toml index 8654564..a80fdb7 100644 --- a/pipeline_core/pyproject.toml +++ b/pipeline_core/pyproject.toml @@ -1,8 +1,9 @@ [project] name = "pipeline_core" version = "0.1.0" -description = "产线管理核心模块 - 产线定义、步骤配置与发布管理" -dependencies = [ - "sqlor", - "bricks_for_python", -] +description = "产线管理核心模块" +dependencies = ["sqlor", "bricks_for_python"] + +[tool.setuptools.packages.find] +where = ["."] +include = ["pipeline_core*"] diff --git a/pipeline_core/wwwroot/pipeline_steps/add_pipeline_steps.dspy b/pipeline_core/wwwroot/pipeline_steps/add_pipeline_steps.dspy new file mode 100644 index 0000000..098b2fc --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_steps/add_pipeline_steps.dspy @@ -0,0 +1,37 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('pipeline_steps', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "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/pipeline_core/wwwroot/pipeline_steps/delete_pipeline_steps.dspy b/pipeline_core/wwwroot/pipeline_steps/delete_pipeline_steps.dspy new file mode 100644 index 0000000..d659f20 --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_steps/delete_pipeline_steps.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('pipeline_steps', 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/pipeline_core/wwwroot/pipeline_steps/get_pipeline_steps.dspy b/pipeline_core/wwwroot/pipeline_steps/get_pipeline_steps.dspy new file mode 100644 index 0000000..48c6209 --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_steps/get_pipeline_steps.dspy @@ -0,0 +1,130 @@ + +ns = params_kw.copy() + + +debug(f'get_pipeline_steps.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'step_order' + + + +sql = '''select a.*, b.pipeline_id_text +from (select * from pipeline_steps where 1=1 [[filterstr]]) a left join (select id as pipeline_id, + name as pipeline_id_text from pipelines where 1 = 1) b on a.pipeline_id = b.pipeline_id''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "pipeline_id", + "title": "所属产线", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "step_order", + "title": "步骤序号", + "type": "int", + "nullable": "no" + }, + { + "name": "step_name", + "title": "步骤名称", + "type": "str", + "length": 100, + "nullable": "no" + }, + { + "name": "step_type", + "title": "步骤类型", + "type": "str", + "length": 50, + "nullable": "no" + }, + { + "name": "model_name", + "title": "调用模型名称", + "type": "str", + "length": 100 + }, + { + "name": "step_config", + "title": "步骤配置JSON", + "type": "text" + }, + { + "name": "input_schema", + "title": "输入定义JSON", + "type": "text" + }, + { + "name": "output_schema", + "title": "输出定义JSON", + "type": "text" + }, + { + "name": "timeout_seconds", + "title": "超时秒数", + "type": "int", + "default": "300" + }, + { + "name": "retry_count", + "title": "重试次数", + "type": "int", + "default": "0" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_core') +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/pipeline_core/wwwroot/pipeline_steps/index.ui b/pipeline_core/wwwroot/pipeline_steps/index.ui new file mode 100644 index 0000000..4e2f82d --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_steps/index.ui @@ -0,0 +1,241 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"pipeline_steps_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"产线步骤表", + + + + + "toolbar":{ + "tools": [ + { + "selected_row": true, + "name": "pipelines", + "icon": "{{entire_url('/imgs/pipelines.svg')}}", + "label": "所属产线" + } + ] +}, + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_pipeline_steps.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_pipeline_steps.dspy')}}", + + + "update_data_url":"{{entire_url('update_pipeline_steps.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_pipeline_steps.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id", + "step_config", + "input_schema", + "output_schema" + ], + "cwidth": {} +}, + + + "editexclouded":[ + "id", + "created_at" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "pipeline_id", + "title": "所属产线", + "type": "str", + "length": 32, + "nullable": "no", + "label": "所属产线", + "uitype": "code", + "valueField": "pipeline_id", + "textField": "pipeline_id_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_core')}}", + "table": "pipelines", + "tblvalue": "id", + "tbltext": "name", + "valueField": "pipeline_id", + "textField": "pipeline_id_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "step_order", + "title": "步骤序号", + "type": "int", + "nullable": "no", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "步骤序号" + }, + { + "name": "step_name", + "title": "步骤名称", + "type": "str", + "length": 100, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "步骤名称" + }, + { + "name": "step_type", + "title": "步骤类型", + "type": "str", + "length": 50, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "步骤类型" + }, + { + "name": "model_name", + "title": "调用模型名称", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "调用模型名称" + }, + { + "name": "step_config", + "title": "步骤配置JSON", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "步骤配置JSON" + }, + { + "name": "input_schema", + "title": "输入定义JSON", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "输入定义JSON" + }, + { + "name": "output_schema", + "title": "输出定义JSON", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "输出定义JSON" + }, + { + "name": "timeout_seconds", + "title": "超时秒数", + "type": "int", + "default": "300", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "超时秒数" + }, + { + "name": "retry_count", + "title": "重试次数", + "type": "int", + "default": "0", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "重试次数" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "创建时间" + } +] + }, + + + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[ + { + "wid": "self", + "event": "pipelines", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "所属产线", + "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=pipelines", + "resizable": true, + "height": "70%", + "width": "70%" + }, + "params_mapping": { + "mapping": { + "id": "pipeline_id", + "referer_widget": "referer_widget" + }, + "need_other": false + }, + "options": { + "method": "POST", + "params": {}, + "url": "{{entire_url('../pipelines')}}" + } + } +] + +}] +} \ No newline at end of file diff --git a/pipeline_core/wwwroot/pipeline_steps/update_pipeline_steps.dspy b/pipeline_core/wwwroot/pipeline_steps/update_pipeline_steps.dspy new file mode 100644 index 0000000..9c095da --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_steps/update_pipeline_steps.dspy @@ -0,0 +1,36 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('pipeline_steps', 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/pipeline_core/wwwroot/pipeline_versions/add_pipeline_versions.dspy b/pipeline_core/wwwroot/pipeline_versions/add_pipeline_versions.dspy new file mode 100644 index 0000000..15c2ddb --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_versions/add_pipeline_versions.dspy @@ -0,0 +1,37 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('pipeline_versions', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "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/pipeline_core/wwwroot/pipeline_versions/delete_pipeline_versions.dspy b/pipeline_core/wwwroot/pipeline_versions/delete_pipeline_versions.dspy new file mode 100644 index 0000000..42a6efb --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_versions/delete_pipeline_versions.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('pipeline_versions', 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/pipeline_core/wwwroot/pipeline_versions/get_pipeline_versions.dspy b/pipeline_core/wwwroot/pipeline_versions/get_pipeline_versions.dspy new file mode 100644 index 0000000..b039ebd --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_versions/get_pipeline_versions.dspy @@ -0,0 +1,113 @@ + +ns = params_kw.copy() + + +debug(f'get_pipeline_versions.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'created_at' + + + +sql = '''select a.*, b.pipeline_id_text +from (select * from pipeline_versions where 1=1 [[filterstr]]) a left join (select id as pipeline_id, + name as pipeline_id_text from pipelines where 1 = 1) b on a.pipeline_id = b.pipeline_id''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "version", + "title": "版本号", + "type": "str", + "length": 20, + "nullable": "no" + }, + { + "name": "publish_status", + "title": "发布状态", + "type": "str", + "length": 20, + "nullable": "no", + "default": "pending" + }, + { + "name": "published_by", + "title": "发布人", + "type": "str", + "length": 32 + }, + { + "name": "published_at", + "title": "发布时间", + "type": "timestamp" + }, + { + "name": "changelog", + "title": "变更说明", + "type": "text" + }, + { + "name": "config_snapshot", + "title": "配置快照JSON", + "type": "text" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_core') +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/pipeline_core/wwwroot/pipeline_versions/index.ui b/pipeline_core/wwwroot/pipeline_versions/index.ui new file mode 100644 index 0000000..05c27a9 --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_versions/index.ui @@ -0,0 +1,210 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"pipeline_versions_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"产线发布记录表", + + + + + "toolbar":{ + "tools": [ + { + "selected_row": true, + "name": "pipelines", + "icon": "{{entire_url('/imgs/pipelines.svg')}}", + "label": "所属产线" + } + ] +}, + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_pipeline_versions.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_pipeline_versions.dspy')}}", + + + "update_data_url":"{{entire_url('update_pipeline_versions.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_pipeline_versions.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id", + "config_snapshot" + ], + "cwidth": {} +}, + + + "editexclouded":[ + "id", + "created_at" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no", + "label": "产线ID", + "uitype": "code", + "valueField": "pipeline_id", + "textField": "pipeline_id_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_core')}}", + "table": "pipelines", + "tblvalue": "id", + "tbltext": "name", + "valueField": "pipeline_id", + "textField": "pipeline_id_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "version", + "title": "版本号", + "type": "str", + "length": 20, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "版本号" + }, + { + "name": "publish_status", + "title": "发布状态", + "type": "str", + "length": 20, + "nullable": "no", + "default": "pending", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "发布状态" + }, + { + "name": "published_by", + "title": "发布人", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "发布人" + }, + { + "name": "published_at", + "title": "发布时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "发布时间" + }, + { + "name": "changelog", + "title": "变更说明", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "变更说明" + }, + { + "name": "config_snapshot", + "title": "配置快照JSON", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "配置快照JSON" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "创建时间" + } +] + }, + + + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[ + { + "wid": "self", + "event": "pipelines", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "所属产线", + "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=pipelines", + "resizable": true, + "height": "70%", + "width": "70%" + }, + "params_mapping": { + "mapping": { + "id": "pipeline_id", + "referer_widget": "referer_widget" + }, + "need_other": false + }, + "options": { + "method": "POST", + "params": {}, + "url": "{{entire_url('../pipelines')}}" + } + } +] + +}] +} \ No newline at end of file diff --git a/pipeline_core/wwwroot/pipeline_versions/update_pipeline_versions.dspy b/pipeline_core/wwwroot/pipeline_versions/update_pipeline_versions.dspy new file mode 100644 index 0000000..74b8082 --- /dev/null +++ b/pipeline_core/wwwroot/pipeline_versions/update_pipeline_versions.dspy @@ -0,0 +1,36 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('pipeline_versions', 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/pipeline_core/wwwroot/pipelines/add_pipelines.dspy b/pipeline_core/wwwroot/pipelines/add_pipelines.dspy new file mode 100644 index 0000000..9972d78 --- /dev/null +++ b/pipeline_core/wwwroot/pipelines/add_pipelines.dspy @@ -0,0 +1,54 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + +if params_kw.get('model_api_key'): + ns['model_api_key'] = password_encode(params_kw.get('model_api_key')) + + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['org_id'] = userorgid + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('pipelines', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "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/pipeline_core/wwwroot/pipelines/delete_pipelines.dspy b/pipeline_core/wwwroot/pipelines/delete_pipelines.dspy new file mode 100644 index 0000000..f2318f0 --- /dev/null +++ b/pipeline_core/wwwroot/pipelines/delete_pipelines.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['org_id'] = userorgid + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('pipelines', 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/pipeline_core/wwwroot/pipelines/get_pipelines.dspy b/pipeline_core/wwwroot/pipelines/get_pipelines.dspy new file mode 100644 index 0000000..129b5d1 --- /dev/null +++ b/pipeline_core/wwwroot/pipelines/get_pipelines.dspy @@ -0,0 +1,165 @@ + +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['org_id'] = userorgid +ns['userorgid'] = userorgid + +debug(f'get_pipelines.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'created_at' + + + +sql = '''select a.*, b.pipeline_type_text, c.status_text +from (select * from pipelines where 1=1 [[filterstr]]) a left join (select k as pipeline_type, + v as pipeline_type_text from appcodes_kv where parentid='pipeline_type') b on a.pipeline_type = b.pipeline_type left join (select k as status, + v as status_text from appcodes_kv where parentid='pipeline_status') c on a.status = c.status''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "name", + "title": "产线名称", + "type": "str", + "length": 200, + "nullable": "no" + }, + { + "name": "description", + "title": "产线描述", + "type": "text" + }, + { + "name": "pipeline_type", + "title": "产线类型", + "type": "str", + "length": 50, + "nullable": "no" + }, + { + "name": "version", + "title": "当前版本", + "type": "str", + "length": 20, + "default": "1.0.0" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "nullable": "no", + "default": "draft" + }, + { + "name": "pipeline_config", + "title": "产线配置JSON", + "type": "text" + }, + { + "name": "model_api_url", + "title": "模型API地址", + "type": "str", + "length": 500 + }, + { + "name": "model_api_key", + "title": "模型API密钥", + "type": "str", + "length": 500 + }, + { + "name": "org_id", + "title": "所属机构ID", + "type": "str", + "length": 32, + "default": "0" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +# 确保 logined 过滤条件始终生效 +if filterjson: + if not isinstance(filterjson, dict) or 'AND' not in filterjson: + filterjson = {'AND': [filterjson] if filterjson else []} + + filterjson['AND'].append({'field': 'org_id', 'op': '=', 'var': '__logined_orgid__'}) + ns['__logined_orgid__'] = userorgid + + + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_core') +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/pipeline_core/wwwroot/pipelines/index.ui b/pipeline_core/wwwroot/pipelines/index.ui new file mode 100644 index 0000000..436c9b7 --- /dev/null +++ b/pipeline_core/wwwroot/pipelines/index.ui @@ -0,0 +1,228 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"pipelines_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"产线定义表", + + + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_pipelines.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_pipelines.dspy')}}", + + + "update_data_url":"{{entire_url('update_pipelines.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_pipelines.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id", + "model_api_key", + "pipeline_config" + ], + "cwidth": {} +}, + + + "editexclouded":[ + "id", + "created_at", + "updated_at" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "name", + "title": "产线名称", + "type": "str", + "length": 200, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "产线名称" + }, + { + "name": "description", + "title": "产线描述", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "产线描述" + }, + { + "name": "pipeline_type", + "title": "产线类型", + "type": "str", + "length": 50, + "nullable": "no", + "label": "产线类型", + "uitype": "code", + "valueField": "pipeline_type", + "textField": "pipeline_type_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_core')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "pipeline_type", + "textField": "pipeline_type_text", + "cond": "parentid='pipeline_type'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "version", + "title": "当前版本", + "type": "str", + "length": 20, + "default": "1.0.0", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "当前版本" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "nullable": "no", + "default": "draft", + "label": "状态", + "uitype": "code", + "valueField": "status", + "textField": "status_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_core')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "status", + "textField": "status_text", + "cond": "parentid='pipeline_status'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "pipeline_config", + "title": "产线配置JSON", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "产线配置JSON" + }, + { + "name": "model_api_url", + "title": "模型API地址", + "type": "str", + "length": 500, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "模型API地址" + }, + { + "name": "model_api_key", + "title": "模型API密钥", + "type": "str", + "length": 500, + "cwidth": 18, + "uitype": "password", + "datatype": "str", + "label": "模型API密钥" + }, + { + "name": "org_id", + "title": "所属机构ID", + "type": "str", + "length": 32, + "default": "0", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "所属机构ID" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "创建人" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "创建时间" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "更新时间" + } +] + }, + + + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +}] +} \ No newline at end of file diff --git a/pipeline_core/wwwroot/pipelines/update_pipelines.dspy b/pipeline_core/wwwroot/pipelines/update_pipelines.dspy new file mode 100644 index 0000000..b777b85 --- /dev/null +++ b/pipeline_core/wwwroot/pipelines/update_pipelines.dspy @@ -0,0 +1,73 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['org_id'] = userorgid + + +if params_kw.get('model_api_key'): + ns['model_api_key'] = password_encode(params_kw.get('model_api_key')) + + +db = DBPools() +dbname = get_module_dbname('pipeline_core') +async with db.sqlorContext(dbname) as sor: + + ns1 = { + + "org_id": userorgid, + + + "id": params_kw.id + } + recs = await sor.R('pipelines', ns1) + if len(recs) < 1: + return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"Record no exist or with wrong ownership" + } + } + + r = await sor.U('pipelines', 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/pipeline_dist/mysql.ddl.sql b/pipeline_dist/mysql.ddl.sql new file mode 100644 index 0000000..d018d79 --- /dev/null +++ b/pipeline_dist/mysql.ddl.sql @@ -0,0 +1,83 @@ + +-- models/distributors.json + + + + + +-- 建库时请用以下语句,支持emoji字符 +-- CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +drop table if exists distributors; +CREATE TABLE distributors +( + + `id` VARCHAR(32) NOT NULL comment '主键', + `name` VARCHAR(200) NOT NULL comment '分销商名称', + `org_id` VARCHAR(32) comment '关联组织ID', + `contact_name` VARCHAR(50) comment '联系人', + `contact_phone` VARCHAR(20) comment '联系电话', + `contact_email` VARCHAR(100) comment '联系邮箱', + `api_key` VARCHAR(200) comment 'API密钥', + `commission_rate` double(5,2) DEFAULT '0' comment '佣金比例%', + `status` VARCHAR(20) DEFAULT 'active' comment '状态', + `agreement_start` date comment '协议开始日', + `agreement_end` date comment '协议结束日', + `remarks` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '备注', + `created_by` VARCHAR(32) comment '创建人', + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '创建时间', + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '更新时间' + + +,primary key(id) + + +) +CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci +engine=innodb +comment '分销商表' +; + +CREATE INDEX distributors_idx_dist_name ON distributors(name); +CREATE INDEX distributors_idx_dist_status ON distributors(status); + +-- models/distributor_pipeline.json + + + + + +-- 建库时请用以下语句,支持emoji字符 +-- CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +drop table if exists distributor_pipeline; +CREATE TABLE distributor_pipeline +( + + `id` VARCHAR(32) NOT NULL comment '主键', + `distributor_id` VARCHAR(32) NOT NULL comment '分销商ID', + `pipeline_id` VARCHAR(32) NOT NULL comment '产线ID', + `custom_price` double(15,4) comment '自定义单价', + `markup_type` VARCHAR(20) comment '加价方式', + `markup_value` double(10,2) comment '加价值', + `daily_limit` int DEFAULT '0' comment '日限额', + `monthly_limit` int DEFAULT '0' comment '月限额', + `today_usage` int DEFAULT '0' comment '今日已用', + `month_usage` int DEFAULT '0' comment '本月已用', + `status` VARCHAR(20) DEFAULT 'active' comment '状态', + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '创建时间', + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '更新时间' + + +,primary key(id) + + +) +CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci +engine=innodb +comment '分销商-产线关联定价表' +; + +CREATE UNIQUE INDEX distributor_pipeline_idx_dp_dist_pipe ON distributor_pipeline(distributor_id,pipeline_id); +CREATE INDEX distributor_pipeline_idx_dp_distributor ON distributor_pipeline(distributor_id); + diff --git a/pipeline_dist/pyproject.toml b/pipeline_dist/pyproject.toml index 3036833..af56723 100644 --- a/pipeline_dist/pyproject.toml +++ b/pipeline_dist/pyproject.toml @@ -1,9 +1,9 @@ [project] name = "pipeline_dist" version = "0.1.0" -description = "Pipeline distributor management module" -requires-python = ">=3.8" -dependencies = [ - "sqlor", - "bricks_for_python", -] +description = "产线分发模块" +dependencies = ["sqlor"] + +[tool.setuptools.packages.find] +where = ["."] +include = ["pipeline_dist*"] diff --git a/pipeline_dist/wwwroot/distributor_pipeline/add_distributor_pipeline.dspy b/pipeline_dist/wwwroot/distributor_pipeline/add_distributor_pipeline.dspy new file mode 100644 index 0000000..adaa5c9 --- /dev/null +++ b/pipeline_dist/wwwroot/distributor_pipeline/add_distributor_pipeline.dspy @@ -0,0 +1,37 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = get_module_dbname('pipeline_dist') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('distributor_pipeline', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "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/pipeline_dist/wwwroot/distributor_pipeline/delete_distributor_pipeline.dspy b/pipeline_dist/wwwroot/distributor_pipeline/delete_distributor_pipeline.dspy new file mode 100644 index 0000000..5fea630 --- /dev/null +++ b/pipeline_dist/wwwroot/distributor_pipeline/delete_distributor_pipeline.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('pipeline_dist') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('distributor_pipeline', 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/pipeline_dist/wwwroot/distributor_pipeline/get_distributor_pipeline.dspy b/pipeline_dist/wwwroot/distributor_pipeline/get_distributor_pipeline.dspy new file mode 100644 index 0000000..baf0035 --- /dev/null +++ b/pipeline_dist/wwwroot/distributor_pipeline/get_distributor_pipeline.dspy @@ -0,0 +1,143 @@ + +ns = params_kw.copy() + + +debug(f'get_distributor_pipeline.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'created_at' + + + +sql = '''select a.*, b.distributor_id_text, c.pipeline_id_text, d.markup_type_text, e.status_text +from (select * from distributor_pipeline where 1=1 [[filterstr]]) a left join (select id as distributor_id, + name as distributor_id_text from distributors where 1 = 1) b on a.distributor_id = b.distributor_id left join (select id as pipeline_id, + name as pipeline_id_text from pipelines where 1 = 1) c on a.pipeline_id = c.pipeline_id left join (select k as markup_type, + v as markup_type_text from appcodes_kv where parentid='markup_type') d on a.markup_type = d.markup_type left join (select k as status, + v as status_text from appcodes_kv where parentid='dp_status') e on a.status = e.status''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "distributor_id", + "title": "分销商ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "custom_price", + "title": "自定义单价", + "type": "double", + "length": 15, + "dec": 4 + }, + { + "name": "markup_type", + "title": "加价方式", + "type": "str", + "length": 20 + }, + { + "name": "markup_value", + "title": "加价值", + "type": "double", + "length": 10, + "dec": 2 + }, + { + "name": "daily_limit", + "title": "日限额", + "type": "int", + "default": "0" + }, + { + "name": "monthly_limit", + "title": "月限额", + "type": "int", + "default": "0" + }, + { + "name": "today_usage", + "title": "今日已用", + "type": "int", + "default": "0" + }, + { + "name": "month_usage", + "title": "本月已用", + "type": "int", + "default": "0" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "default": "active" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_dist') +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/pipeline_dist/wwwroot/distributor_pipeline/index.ui b/pipeline_dist/wwwroot/distributor_pipeline/index.ui new file mode 100644 index 0000000..59a21fe --- /dev/null +++ b/pipeline_dist/wwwroot/distributor_pipeline/index.ui @@ -0,0 +1,327 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"distributor_pipeline_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"分销商-产线关联定价表", + + + + + "toolbar":{ + "tools": [ + { + "selected_row": true, + "name": "distributors", + "icon": "{{entire_url('/imgs/distributors.svg')}}", + "label": "分销商" + }, + { + "selected_row": true, + "name": "pipelines", + "icon": "{{entire_url('/imgs/pipelines.svg')}}", + "label": "产线" + } + ] +}, + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_distributor_pipeline.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_distributor_pipeline.dspy')}}", + + + "update_data_url":"{{entire_url('update_distributor_pipeline.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_distributor_pipeline.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id" + ], + "cwidth": {}, + "alters": { + "distributor_id": { + "dataurl": "{{entire_url('../api/get_search_distributor_id.dspy')}}" + }, + "pipeline_id": { + "dataurl": "{{entire_url('../api/get_search_pipeline_id.dspy')}}" + } + } +}, + + + "editexclouded":[ + "id", + "created_at", + "updated_at" +], + + "fields":[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "主键" + }, + { + "name": "distributor_id", + "title": "分销商ID", + "type": "str", + "length": 32, + "nullable": "no", + "label": "分销商ID", + "uitype": "code", + "valueField": "distributor_id", + "textField": "distributor_id_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_dist')}}", + "table": "distributors", + "tblvalue": "id", + "tbltext": "name", + "valueField": "distributor_id", + "textField": "distributor_id_text" + }, + "dataurl": "{{entire_url('../api/get_search_distributor_id.dspy')}}" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no", + "label": "产线ID", + "uitype": "code", + "valueField": "pipeline_id", + "textField": "pipeline_id_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_dist')}}", + "table": "pipelines", + "tblvalue": "id", + "tbltext": "name", + "valueField": "pipeline_id", + "textField": "pipeline_id_text" + }, + "dataurl": "{{entire_url('../api/get_search_pipeline_id.dspy')}}" + }, + { + "name": "custom_price", + "title": "自定义单价", + "type": "double", + "length": 15, + "dec": 4, + "cwidth": 15, + "uitype": "float", + "datatype": "double", + "label": "自定义单价" + }, + { + "name": "markup_type", + "title": "加价方式", + "type": "str", + "length": 20, + "label": "加价方式", + "uitype": "code", + "valueField": "markup_type", + "textField": "markup_type_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_dist')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "markup_type", + "textField": "markup_type_text", + "cond": "parentid='markup_type'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "markup_value", + "title": "加价值", + "type": "double", + "length": 10, + "dec": 2, + "cwidth": 10, + "uitype": "float", + "datatype": "double", + "label": "加价值" + }, + { + "name": "daily_limit", + "title": "日限额", + "type": "int", + "default": "0", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "日限额" + }, + { + "name": "monthly_limit", + "title": "月限额", + "type": "int", + "default": "0", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "月限额" + }, + { + "name": "today_usage", + "title": "今日已用", + "type": "int", + "default": "0", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "今日已用" + }, + { + "name": "month_usage", + "title": "本月已用", + "type": "int", + "default": "0", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "本月已用" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "default": "active", + "label": "状态", + "uitype": "code", + "valueField": "status", + "textField": "status_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_dist')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "status", + "textField": "status_text", + "cond": "parentid='dp_status'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "创建时间" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "更新时间" + } +] + }, + + + + "data_filter":{ + "distributor_id": "", + "pipeline_id": "", + "status": "" +}, + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[ + { + "wid": "self", + "event": "distributors", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "分销商", + "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=distributors", + "resizable": true, + "height": "70%", + "width": "70%" + }, + "params_mapping": { + "mapping": { + "id": "distributor_id", + "referer_widget": "referer_widget" + }, + "need_other": false + }, + "options": { + "method": "POST", + "params": {}, + "url": "{{entire_url('../distributors')}}" + } + }, + { + "wid": "self", + "event": "pipelines", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "产线", + "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=pipelines", + "resizable": true, + "height": "70%", + "width": "70%" + }, + "params_mapping": { + "mapping": { + "id": "pipeline_id", + "referer_widget": "referer_widget" + }, + "need_other": false + }, + "options": { + "method": "POST", + "params": {}, + "url": "{{entire_url('../pipelines')}}" + } + } +] + +}] +} \ No newline at end of file diff --git a/pipeline_dist/wwwroot/distributor_pipeline/update_distributor_pipeline.dspy b/pipeline_dist/wwwroot/distributor_pipeline/update_distributor_pipeline.dspy new file mode 100644 index 0000000..3569181 --- /dev/null +++ b/pipeline_dist/wwwroot/distributor_pipeline/update_distributor_pipeline.dspy @@ -0,0 +1,36 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + + +db = DBPools() +dbname = get_module_dbname('pipeline_dist') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('distributor_pipeline', 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/pipeline_dist/wwwroot/distributors/add_distributors.dspy b/pipeline_dist/wwwroot/distributors/add_distributors.dspy new file mode 100644 index 0000000..bb8871f --- /dev/null +++ b/pipeline_dist/wwwroot/distributors/add_distributors.dspy @@ -0,0 +1,40 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + +if params_kw.get('api_key'): + ns['api_key'] = password_encode(params_kw.get('api_key')) + + + +db = DBPools() +dbname = get_module_dbname('pipeline_dist') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('distributors', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "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/pipeline_dist/wwwroot/distributors/delete_distributors.dspy b/pipeline_dist/wwwroot/distributors/delete_distributors.dspy new file mode 100644 index 0000000..8f8d3e3 --- /dev/null +++ b/pipeline_dist/wwwroot/distributors/delete_distributors.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('pipeline_dist') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('distributors', 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/pipeline_dist/wwwroot/distributors/get_distributors.dspy b/pipeline_dist/wwwroot/distributors/get_distributors.dspy new file mode 100644 index 0000000..d5f7ea7 --- /dev/null +++ b/pipeline_dist/wwwroot/distributors/get_distributors.dspy @@ -0,0 +1,148 @@ + +ns = params_kw.copy() + + +debug(f'get_distributors.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'created_at' + + + +sql = '''select a.*, b.status_text +from (select * from distributors where 1=1 [[filterstr]]) a left join (select k as status, + v as status_text from appcodes_kv where parentid='distributor_status') b on a.status = b.status''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "name", + "title": "分销商名称", + "type": "str", + "length": 200, + "nullable": "no" + }, + { + "name": "org_id", + "title": "关联组织ID", + "type": "str", + "length": 32 + }, + { + "name": "contact_name", + "title": "联系人", + "type": "str", + "length": 50 + }, + { + "name": "contact_phone", + "title": "联系电话", + "type": "str", + "length": 20 + }, + { + "name": "contact_email", + "title": "联系邮箱", + "type": "str", + "length": 100 + }, + { + "name": "api_key", + "title": "API密钥", + "type": "str", + "length": 200 + }, + { + "name": "commission_rate", + "title": "佣金比例%", + "type": "double", + "length": 5, + "dec": 2, + "default": "0" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "default": "active" + }, + { + "name": "agreement_start", + "title": "协议开始日", + "type": "date" + }, + { + "name": "agreement_end", + "title": "协议结束日", + "type": "date" + }, + { + "name": "remarks", + "title": "备注", + "type": "text" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_dist') +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/pipeline_dist/wwwroot/distributors/index.ui b/pipeline_dist/wwwroot/distributors/index.ui new file mode 100644 index 0000000..844faf7 --- /dev/null +++ b/pipeline_dist/wwwroot/distributors/index.ui @@ -0,0 +1,240 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"distributors_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"分销商表", + + + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_distributors.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_distributors.dspy')}}", + + + "update_data_url":"{{entire_url('update_distributors.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_distributors.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id", + "api_key", + "remarks" + ], + "cwidth": {} +}, + + + "editexclouded":[ + "id", + "created_at", + "updated_at" +], + + "fields":[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "主键" + }, + { + "name": "name", + "title": "分销商名称", + "type": "str", + "length": 200, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "分销商名称" + }, + { + "name": "org_id", + "title": "关联组织ID", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "关联组织ID" + }, + { + "name": "contact_name", + "title": "联系人", + "type": "str", + "length": 50, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "联系人" + }, + { + "name": "contact_phone", + "title": "联系电话", + "type": "str", + "length": 20, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "联系电话" + }, + { + "name": "contact_email", + "title": "联系邮箱", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "联系邮箱" + }, + { + "name": "api_key", + "title": "API密钥", + "type": "str", + "length": 200, + "cwidth": 18, + "uitype": "password", + "datatype": "str", + "label": "API密钥" + }, + { + "name": "commission_rate", + "title": "佣金比例%", + "type": "double", + "length": 5, + "dec": 2, + "default": "0", + "cwidth": 5, + "uitype": "float", + "datatype": "double", + "label": "佣金比例%" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "default": "active", + "label": "状态", + "uitype": "code", + "valueField": "status", + "textField": "status_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_dist')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "status", + "textField": "status_text", + "cond": "parentid='distributor_status'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "agreement_start", + "title": "协议开始日", + "type": "date", + "length": 0, + "uitype": "date", + "datatype": "date", + "label": "协议开始日" + }, + { + "name": "agreement_end", + "title": "协议结束日", + "type": "date", + "length": 0, + "uitype": "date", + "datatype": "date", + "label": "协议结束日" + }, + { + "name": "remarks", + "title": "备注", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "备注" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "创建人" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "创建时间" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "更新时间" + } +] + }, + + + + "data_filter":{ + "name": "", + "status": "" +}, + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +}] +} \ No newline at end of file diff --git a/pipeline_dist/wwwroot/distributors/update_distributors.dspy b/pipeline_dist/wwwroot/distributors/update_distributors.dspy new file mode 100644 index 0000000..f348d31 --- /dev/null +++ b/pipeline_dist/wwwroot/distributors/update_distributors.dspy @@ -0,0 +1,39 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + +if params_kw.get('api_key'): + ns['api_key'] = password_encode(params_kw.get('api_key')) + + +db = DBPools() +dbname = get_module_dbname('pipeline_dist') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('distributors', 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/pipeline_ops/mysql.ddl.sql b/pipeline_ops/mysql.ddl.sql new file mode 100644 index 0000000..b5f697d --- /dev/null +++ b/pipeline_ops/mysql.ddl.sql @@ -0,0 +1,116 @@ + +-- models/pipeline_capacity.json + + + + + +-- 建库时请用以下语句,支持emoji字符 +-- CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +drop table if exists pipeline_capacity; +CREATE TABLE pipeline_capacity +( + + `id` VARCHAR(32) NOT NULL comment '主键', + `pipeline_id` VARCHAR(32) NOT NULL comment '产线ID', + `max_concurrent` int comment '最大并发数', + `daily_limit` int comment '每日限额', + `monthly_limit` int comment '每月限额', + `today_usage` int comment '今日用量', + `month_usage` int comment '本月用量', + `status` VARCHAR(20) DEFAULT 'active' comment '状态', + `org_id` VARCHAR(32) DEFAULT '0' comment '所属组织', + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '更新时间' + + +,primary key(id) + + +) +CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci +engine=innodb +comment '产线容量表' +; + +CREATE UNIQUE INDEX pipeline_capacity_uk_capacity_pipeline ON pipeline_capacity(pipeline_id); + +-- models/pipeline_pricing.json + + + + + +-- 建库时请用以下语句,支持emoji字符 +-- CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +drop table if exists pipeline_pricing; +CREATE TABLE pipeline_pricing +( + + `id` VARCHAR(32) NOT NULL comment '主键', + `pipeline_id` VARCHAR(32) NOT NULL comment '产线ID', + `pricing_type` VARCHAR(20) NOT NULL comment '计费方式', + `unit_price` double(15,4) NOT NULL DEFAULT '0' comment '单价', + `currency` VARCHAR(10) DEFAULT 'CNY' comment '货币', + `pricing_config` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '定价配置JSON', + `effective_date` date comment '生效日期', + `expiry_date` date comment '失效日期', + `status` VARCHAR(20) DEFAULT 'active' comment '状态', + `org_id` VARCHAR(32) DEFAULT '0' comment '所属组织', + `created_by` VARCHAR(32) comment '创建人', + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '创建时间', + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '更新时间' + + +,primary key(id) + + +) +CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci +engine=innodb +comment '产线定价表' +; + +CREATE INDEX pipeline_pricing_idx_pricing_pipeline ON pipeline_pricing(pipeline_id); +CREATE INDEX pipeline_pricing_idx_pricing_status_date ON pipeline_pricing(status,effective_date); + +-- models/pipeline_usage_log.json + + + + + +-- 建库时请用以下语句,支持emoji字符 +-- CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +drop table if exists pipeline_usage_log; +CREATE TABLE pipeline_usage_log +( + + `id` VARCHAR(32) NOT NULL comment '主键', + `pipeline_id` VARCHAR(32) NOT NULL comment '产线ID', + `user_id` VARCHAR(32) comment '用户ID', + `distributor_id` VARCHAR(32) comment '分销商ID', + `call_count` int DEFAULT '1' comment '调用次数', + `token_input` int DEFAULT '0' comment '输入Token数', + `token_output` int DEFAULT '0' comment '输出Token数', + `amount` double(15,4) DEFAULT '0' comment '金额', + `status` VARCHAR(20) comment '状态', + `error_message` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci comment '错误信息', + `called_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '调用时间' + + +,primary key(id) + + +) +CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci +engine=innodb +comment '产线调用日志表' +; + +CREATE INDEX pipeline_usage_log_idx_usagelog_pipeline ON pipeline_usage_log(pipeline_id); +CREATE INDEX pipeline_usage_log_idx_usagelog_called_at ON pipeline_usage_log(called_at); +CREATE INDEX pipeline_usage_log_idx_usagelog_distributor ON pipeline_usage_log(distributor_id); + diff --git a/pipeline_ops/pyproject.toml b/pipeline_ops/pyproject.toml index d807047..5e54f12 100644 --- a/pipeline_ops/pyproject.toml +++ b/pipeline_ops/pyproject.toml @@ -1,12 +1,9 @@ [project] name = "pipeline_ops" -version = "1.0.0" -description = "Pipeline operations module - pricing, capacity, and usage tracking" -dependencies = [ - "sqlor", - "bricks_for_python" -] +version = "0.1.0" +description = "产线运维模块" +dependencies = ["sqlor"] -[build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" +[tool.setuptools.packages.find] +where = ["."] +include = ["pipeline_ops*"] diff --git a/pipeline_ops/wwwroot/pipeline_capacity/add_pipeline_capacity.dspy b/pipeline_ops/wwwroot/pipeline_capacity/add_pipeline_capacity.dspy new file mode 100644 index 0000000..488d631 --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_capacity/add_pipeline_capacity.dspy @@ -0,0 +1,51 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['org_id'] = userorgid + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('pipeline_capacity', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "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/pipeline_ops/wwwroot/pipeline_capacity/delete_pipeline_capacity.dspy b/pipeline_ops/wwwroot/pipeline_capacity/delete_pipeline_capacity.dspy new file mode 100644 index 0000000..9ab1448 --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_capacity/delete_pipeline_capacity.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['org_id'] = userorgid + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('pipeline_capacity', 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/pipeline_ops/wwwroot/pipeline_capacity/get_pipeline_capacity.dspy b/pipeline_ops/wwwroot/pipeline_capacity/get_pipeline_capacity.dspy new file mode 100644 index 0000000..bbb9db4 --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_capacity/get_pipeline_capacity.dspy @@ -0,0 +1,142 @@ + +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['org_id'] = userorgid +ns['userorgid'] = userorgid + +debug(f'get_pipeline_capacity.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = ["pipeline_id"] + + + +sql = '''select a.*, b.pipeline_id_text, c.status_text +from (select * from pipeline_capacity where 1=1 [[filterstr]]) a left join (select id as pipeline_id, + name as pipeline_id_text from pipelines where 1 = 1) b on a.pipeline_id = b.pipeline_id left join (select k as status, + v as status_text from appcodes_kv where parentid='capacity_status') c on a.status = c.status''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "max_concurrent", + "title": "最大并发数", + "type": "int" + }, + { + "name": "daily_limit", + "title": "每日限额", + "type": "int" + }, + { + "name": "monthly_limit", + "title": "每月限额", + "type": "int" + }, + { + "name": "today_usage", + "title": "今日用量", + "type": "int" + }, + { + "name": "month_usage", + "title": "本月用量", + "type": "int" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "default": "active" + }, + { + "name": "org_id", + "title": "所属组织", + "type": "str", + "length": 32, + "default": "0" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +# 确保 logined 过滤条件始终生效 +if filterjson: + if not isinstance(filterjson, dict) or 'AND' not in filterjson: + filterjson = {'AND': [filterjson] if filterjson else []} + + filterjson['AND'].append({'field': 'org_id', 'op': '=', 'var': '__logined_orgid__'}) + ns['__logined_orgid__'] = userorgid + + + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +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/pipeline_ops/wwwroot/pipeline_capacity/index.ui b/pipeline_ops/wwwroot/pipeline_capacity/index.ui new file mode 100644 index 0000000..635448f --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_capacity/index.ui @@ -0,0 +1,227 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"pipeline_capacity_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"产线容量表", + + + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_pipeline_capacity.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_pipeline_capacity.dspy')}}", + + + "update_data_url":"{{entire_url('update_pipeline_capacity.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_pipeline_capacity.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id" + ], + "alters": { + "pipeline_id": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_search_pipeline_id.dspy')}}" + }, + "status": { + "uitype": "code", + "data": [ + { + "value": "active", + "text": "生效中" + }, + { + "value": "disabled", + "text": "已停用" + } + ] + } + } +}, + + + "fields":[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "主键" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no", + "label": "产线ID", + "uitype": "code", + "valueField": "pipeline_id", + "textField": "pipeline_id_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_ops')}}", + "table": "pipelines", + "tblvalue": "id", + "tbltext": "name", + "valueField": "pipeline_id", + "textField": "pipeline_id_text" + }, + "dataurl": "{{entire_url('../api/get_search_pipeline_id.dspy')}}" + }, + { + "name": "max_concurrent", + "title": "最大并发数", + "type": "int", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "最大并发数" + }, + { + "name": "daily_limit", + "title": "每日限额", + "type": "int", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "每日限额" + }, + { + "name": "monthly_limit", + "title": "每月限额", + "type": "int", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "每月限额" + }, + { + "name": "today_usage", + "title": "今日用量", + "type": "int", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "今日用量" + }, + { + "name": "month_usage", + "title": "本月用量", + "type": "int", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "本月用量" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "default": "active", + "label": "状态", + "uitype": "code", + "valueField": "status", + "textField": "status_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_ops')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "status", + "textField": "status_text", + "cond": "parentid='capacity_status'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}", + "data": [ + { + "value": "active", + "text": "生效中" + }, + { + "value": "disabled", + "text": "已停用" + } + ] + }, + { + "name": "org_id", + "title": "所属组织", + "type": "str", + "length": 32, + "default": "0", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "所属组织" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "更新时间" + } +] + }, + + + + "data_filter":{ + "AND": [ + { + "field": "pipeline_id", + "op": "=", + "var": "filter_pipeline_id" + }, + { + "field": "status", + "op": "=", + "var": "filter_status" + } + ] +}, + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +}] +} \ No newline at end of file diff --git a/pipeline_ops/wwwroot/pipeline_capacity/update_pipeline_capacity.dspy b/pipeline_ops/wwwroot/pipeline_capacity/update_pipeline_capacity.dspy new file mode 100644 index 0000000..68f644b --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_capacity/update_pipeline_capacity.dspy @@ -0,0 +1,70 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['org_id'] = userorgid + + + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + + ns1 = { + + "org_id": userorgid, + + + "id": params_kw.id + } + recs = await sor.R('pipeline_capacity', ns1) + if len(recs) < 1: + return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"Record no exist or with wrong ownership" + } + } + + r = await sor.U('pipeline_capacity', 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/pipeline_ops/wwwroot/pipeline_pricing/add_pipeline_pricing.dspy b/pipeline_ops/wwwroot/pipeline_pricing/add_pipeline_pricing.dspy new file mode 100644 index 0000000..f0c8e09 --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_pricing/add_pipeline_pricing.dspy @@ -0,0 +1,51 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['org_id'] = userorgid + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('pipeline_pricing', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "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/pipeline_ops/wwwroot/pipeline_pricing/delete_pipeline_pricing.dspy b/pipeline_ops/wwwroot/pipeline_pricing/delete_pipeline_pricing.dspy new file mode 100644 index 0000000..426926c --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_pricing/delete_pipeline_pricing.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['org_id'] = userorgid + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('pipeline_pricing', 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/pipeline_ops/wwwroot/pipeline_pricing/get_pipeline_pricing.dspy b/pipeline_ops/wwwroot/pipeline_pricing/get_pipeline_pricing.dspy new file mode 100644 index 0000000..a5d4dcb --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_pricing/get_pipeline_pricing.dspy @@ -0,0 +1,168 @@ + +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['org_id'] = userorgid +ns['userorgid'] = userorgid + +debug(f'get_pipeline_pricing.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = ["effective_date desc"] + + + +sql = '''select a.*, b.pipeline_id_text, c.pricing_type_text, d.currency_text, e.status_text +from (select * from pipeline_pricing where 1=1 [[filterstr]]) a left join (select id as pipeline_id, + name as pipeline_id_text from pipelines where 1 = 1) b on a.pipeline_id = b.pipeline_id left join (select k as pricing_type, + v as pricing_type_text from appcodes_kv where parentid='pricing_type') c on a.pricing_type = c.pricing_type left join (select k as currency, + v as currency_text from appcodes_kv where parentid='currency') d on a.currency = d.currency left join (select k as status, + v as status_text from appcodes_kv where parentid='pricing_status') e on a.status = e.status''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "pricing_type", + "title": "计费方式", + "type": "str", + "length": 20, + "nullable": "no" + }, + { + "name": "unit_price", + "title": "单价", + "type": "double", + "length": 15, + "dec": 4, + "nullable": "no", + "default": "0" + }, + { + "name": "currency", + "title": "货币", + "type": "str", + "length": 10, + "default": "CNY" + }, + { + "name": "pricing_config", + "title": "定价配置JSON", + "type": "text" + }, + { + "name": "effective_date", + "title": "生效日期", + "type": "date" + }, + { + "name": "expiry_date", + "title": "失效日期", + "type": "date" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "default": "active" + }, + { + "name": "org_id", + "title": "所属组织", + "type": "str", + "length": 32, + "default": "0" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +# 确保 logined 过滤条件始终生效 +if filterjson: + if not isinstance(filterjson, dict) or 'AND' not in filterjson: + filterjson = {'AND': [filterjson] if filterjson else []} + + filterjson['AND'].append({'field': 'org_id', 'op': '=', 'var': '__logined_orgid__'}) + ns['__logined_orgid__'] = userorgid + + + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +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/pipeline_ops/wwwroot/pipeline_pricing/index.ui b/pipeline_ops/wwwroot/pipeline_pricing/index.ui new file mode 100644 index 0000000..9a54979 --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_pricing/index.ui @@ -0,0 +1,322 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"pipeline_pricing_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"产线定价表", + + + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_pipeline_pricing.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_pipeline_pricing.dspy')}}", + + + "update_data_url":"{{entire_url('update_pipeline_pricing.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_pipeline_pricing.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id" + ], + "alters": { + "pipeline_id": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_search_pipeline_id.dspy')}}" + }, + "pricing_type": { + "uitype": "code", + "data": [ + { + "value": "per_call", + "text": "按次计费" + }, + { + "value": "per_token", + "text": "按token计费" + }, + { + "value": "subscription", + "text": "订阅制" + } + ] + }, + "status": { + "uitype": "code", + "data": [ + { + "value": "active", + "text": "生效中" + }, + { + "value": "expired", + "text": "已过期" + }, + { + "value": "disabled", + "text": "已停用" + } + ] + } + } +}, + + + "fields":[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "主键" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no", + "label": "产线ID", + "uitype": "code", + "valueField": "pipeline_id", + "textField": "pipeline_id_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_ops')}}", + "table": "pipelines", + "tblvalue": "id", + "tbltext": "name", + "valueField": "pipeline_id", + "textField": "pipeline_id_text" + }, + "dataurl": "{{entire_url('../api/get_search_pipeline_id.dspy')}}" + }, + { + "name": "pricing_type", + "title": "计费方式", + "type": "str", + "length": 20, + "nullable": "no", + "label": "计费方式", + "uitype": "code", + "valueField": "pricing_type", + "textField": "pricing_type_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_ops')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "pricing_type", + "textField": "pricing_type_text", + "cond": "parentid='pricing_type'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}", + "data": [ + { + "value": "per_call", + "text": "按次计费" + }, + { + "value": "per_token", + "text": "按token计费" + }, + { + "value": "subscription", + "text": "订阅制" + } + ] + }, + { + "name": "unit_price", + "title": "单价", + "type": "double", + "length": 15, + "dec": 4, + "nullable": "no", + "default": "0", + "cwidth": 15, + "uitype": "float", + "datatype": "double", + "label": "单价" + }, + { + "name": "currency", + "title": "货币", + "type": "str", + "length": 10, + "default": "CNY", + "label": "货币", + "uitype": "code", + "valueField": "currency", + "textField": "currency_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_ops')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "currency", + "textField": "currency_text", + "cond": "parentid='currency'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "pricing_config", + "title": "定价配置JSON", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "定价配置JSON" + }, + { + "name": "effective_date", + "title": "生效日期", + "type": "date", + "length": 0, + "uitype": "date", + "datatype": "date", + "label": "生效日期" + }, + { + "name": "expiry_date", + "title": "失效日期", + "type": "date", + "length": 0, + "uitype": "date", + "datatype": "date", + "label": "失效日期" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "default": "active", + "label": "状态", + "uitype": "code", + "valueField": "status", + "textField": "status_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_ops')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "status", + "textField": "status_text", + "cond": "parentid='pricing_status'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}", + "data": [ + { + "value": "active", + "text": "生效中" + }, + { + "value": "expired", + "text": "已过期" + }, + { + "value": "disabled", + "text": "已停用" + } + ] + }, + { + "name": "org_id", + "title": "所属组织", + "type": "str", + "length": 32, + "default": "0", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "所属组织" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "创建人" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "创建时间" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "更新时间" + } +] + }, + + + + "data_filter":{ + "AND": [ + { + "field": "pipeline_id", + "op": "=", + "var": "filter_pipeline_id" + }, + { + "field": "status", + "op": "=", + "var": "filter_status" + } + ] +}, + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +}] +} \ No newline at end of file diff --git a/pipeline_ops/wwwroot/pipeline_pricing/update_pipeline_pricing.dspy b/pipeline_ops/wwwroot/pipeline_pricing/update_pipeline_pricing.dspy new file mode 100644 index 0000000..d19427c --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_pricing/update_pipeline_pricing.dspy @@ -0,0 +1,70 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['org_id'] = userorgid + + + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + + ns1 = { + + "org_id": userorgid, + + + "id": params_kw.id + } + recs = await sor.R('pipeline_pricing', ns1) + if len(recs) < 1: + return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"Record no exist or with wrong ownership" + } + } + + r = await sor.U('pipeline_pricing', 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/pipeline_ops/wwwroot/pipeline_usage_log/add_pipeline_usage_log.dspy b/pipeline_ops/wwwroot/pipeline_usage_log/add_pipeline_usage_log.dspy new file mode 100644 index 0000000..86ac26d --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_usage_log/add_pipeline_usage_log.dspy @@ -0,0 +1,37 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('pipeline_usage_log', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "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/pipeline_ops/wwwroot/pipeline_usage_log/delete_pipeline_usage_log.dspy b/pipeline_ops/wwwroot/pipeline_usage_log/delete_pipeline_usage_log.dspy new file mode 100644 index 0000000..b93f388 --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_usage_log/delete_pipeline_usage_log.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('pipeline_usage_log', 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/pipeline_ops/wwwroot/pipeline_usage_log/get_pipeline_usage_log.dspy b/pipeline_ops/wwwroot/pipeline_usage_log/get_pipeline_usage_log.dspy new file mode 100644 index 0000000..fdd682c --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_usage_log/get_pipeline_usage_log.dspy @@ -0,0 +1,127 @@ + +ns = params_kw.copy() + + +debug(f'get_pipeline_usage_log.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = ["called_at desc"] + + + +sql = '''select a.*, b.pipeline_id_text, c.status_text +from (select * from pipeline_usage_log where 1=1 [[filterstr]]) a left join (select id as pipeline_id, + name as pipeline_id_text from pipelines where 1 = 1) b on a.pipeline_id = b.pipeline_id left join (select k as status, + v as status_text from appcodes_kv where parentid='usage_status') c on a.status = c.status''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "user_id", + "title": "用户ID", + "type": "str", + "length": 32 + }, + { + "name": "distributor_id", + "title": "分销商ID", + "type": "str", + "length": 32 + }, + { + "name": "call_count", + "title": "调用次数", + "type": "int", + "default": "1" + }, + { + "name": "token_input", + "title": "输入Token数", + "type": "int", + "default": "0" + }, + { + "name": "token_output", + "title": "输出Token数", + "type": "int", + "default": "0" + }, + { + "name": "amount", + "title": "金额", + "type": "double", + "length": 15, + "dec": 4, + "default": "0" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20 + }, + { + "name": "error_message", + "title": "错误信息", + "type": "text" + }, + { + "name": "called_at", + "title": "调用时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +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/pipeline_ops/wwwroot/pipeline_usage_log/index.ui b/pipeline_ops/wwwroot/pipeline_usage_log/index.ui new file mode 100644 index 0000000..50bc9e2 --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_usage_log/index.ui @@ -0,0 +1,265 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"pipeline_usage_log_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"产线调用日志表", + + + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_pipeline_usage_log.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_pipeline_usage_log.dspy')}}", + + + "update_data_url":"{{entire_url('update_pipeline_usage_log.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_pipeline_usage_log.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id" + ], + "alters": { + "pipeline_id": { + "uitype": "code", + "dataurl": "{{entire_url('../api/get_search_pipeline_id.dspy')}}" + }, + "status": { + "uitype": "code", + "data": [ + { + "value": "success", + "text": "成功" + }, + { + "value": "failed", + "text": "失败" + } + ] + } + } +}, + + + "editexclouded":[ + "id", + "pipeline_id", + "user_id", + "distributor_id", + "call_count", + "token_input", + "token_output", + "amount", + "status", + "error_message", + "called_at" +], + + "fields":[ + { + "name": "id", + "title": "主键", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "主键" + }, + { + "name": "pipeline_id", + "title": "产线ID", + "type": "str", + "length": 32, + "nullable": "no", + "label": "产线ID", + "uitype": "code", + "valueField": "pipeline_id", + "textField": "pipeline_id_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_ops')}}", + "table": "pipelines", + "tblvalue": "id", + "tbltext": "name", + "valueField": "pipeline_id", + "textField": "pipeline_id_text" + }, + "dataurl": "{{entire_url('../api/get_search_pipeline_id.dspy')}}" + }, + { + "name": "user_id", + "title": "用户ID", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "用户ID" + }, + { + "name": "distributor_id", + "title": "分销商ID", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "分销商ID" + }, + { + "name": "call_count", + "title": "调用次数", + "type": "int", + "default": "1", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "调用次数" + }, + { + "name": "token_input", + "title": "输入Token数", + "type": "int", + "default": "0", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "输入Token数" + }, + { + "name": "token_output", + "title": "输出Token数", + "type": "int", + "default": "0", + "length": 0, + "uitype": "int", + "datatype": "int", + "label": "输出Token数" + }, + { + "name": "amount", + "title": "金额", + "type": "double", + "length": 15, + "dec": 4, + "default": "0", + "cwidth": 15, + "uitype": "float", + "datatype": "double", + "label": "金额" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "label": "状态", + "uitype": "code", + "valueField": "status", + "textField": "status_text", + "params": { + "dbname": "{{get_module_dbname('pipeline_ops')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "status", + "textField": "status_text", + "cond": "parentid='usage_status'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}", + "data": [ + { + "value": "success", + "text": "成功" + }, + { + "value": "failed", + "text": "失败" + } + ] + }, + { + "name": "error_message", + "title": "错误信息", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "错误信息" + }, + { + "name": "called_at", + "title": "调用时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "调用时间" + } +] + }, + + + + "data_filter":{ + "AND": [ + { + "field": "pipeline_id", + "op": "=", + "var": "filter_pipeline_id" + }, + { + "field": "called_at", + "op": ">=", + "var": "filter_called_at_start" + }, + { + "field": "called_at", + "op": "<=", + "var": "filter_called_at_end" + }, + { + "field": "status", + "op": "=", + "var": "filter_status" + } + ] +}, + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +}] +} \ No newline at end of file diff --git a/pipeline_ops/wwwroot/pipeline_usage_log/update_pipeline_usage_log.dspy b/pipeline_ops/wwwroot/pipeline_usage_log/update_pipeline_usage_log.dspy new file mode 100644 index 0000000..4f88a2e --- /dev/null +++ b/pipeline_ops/wwwroot/pipeline_usage_log/update_pipeline_usage_log.dspy @@ -0,0 +1,36 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + + +db = DBPools() +dbname = get_module_dbname('pipeline_ops') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('pipeline_usage_log', 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/start.sh b/start.sh old mode 100644 new mode 100755 diff --git a/stop.sh b/stop.sh old mode 100644 new mode 100755 diff --git a/wwwroot/appbase b/wwwroot/appbase new file mode 120000 index 0000000..26af5f3 --- /dev/null +++ b/wwwroot/appbase @@ -0,0 +1 @@ +/home/hermesai/repos/pipeline-app/pkgs/appbase/wwwroot \ No newline at end of file diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 3f81beb..b34aea3 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -1,4 +1,5 @@ { + "id": "app", "widgettype": "VBox", "options": { "width": "100%", @@ -14,9 +15,28 @@ "bgcolor": "var(--sage-bg-secondary, #1e293b)", "padding": "0 16px", "alignItems": "center", - "gap": "16px" + "gap": "12px" }, "subwidgets": [ + { + "widgettype": "Button", + "id": "btn_toggle_sidebar", + "options": { + "label": "☰", + "css": "text", + "color": "#fff", + "cfontsize": 1.4 + }, + "binds": [ + { + "wid": "self", + "event": "click", + "actiontype": "method", + "target": "app.sidebar_menu", + "method": "toggle_collapse" + } + ] + }, { "widgettype": "Title4", "options": { @@ -27,35 +47,6 @@ { "widgettype": "Filler" }, - { - "widgettype": "Menu", - "options": { - "target": "-main_content", - "cwidth": 12, - "items": [ - { - "name": "pipeline_core", - "label": "产线管理", - "url": "{{entire_url('/pipeline_core/')}}" - }, - { - "name": "pipeline_ops", - "label": "运营管理", - "url": "{{entire_url('/pipeline_ops/')}}" - }, - { - "name": "pipeline_dist", - "label": "分销管理", - "url": "{{entire_url('/pipeline_dist/')}}" - }, - { - "name": "pipeline_task", - "label": "任务中心", - "url": "{{entire_url('/pipeline_task/')}}" - } - ] - } - }, { "widgettype": "Button", "options": { @@ -68,7 +59,7 @@ "wid": "self", "event": "click", "actiontype": "urlwidget", - "target": "-main_content", + "target": "app.main_content", "options": { "url": "{{entire_url('/rbac/user/')}}" }, @@ -79,117 +70,84 @@ ] }, { - "widgettype": "VScrollPanel", - "id": "main_content", + "widgettype": "HBox", "options": { - "css": "filler", "width": "100%", - "height": "100%" + "css": "filler", + "padding": "0" }, "subwidgets": [ { - "widgettype": "VBox", + "widgettype": "Menu", + "id": "sidebar_menu", "options": { + "width": "200px", + "height": "100%", + "bgcolor": "var(--sage-bg-secondary, #f8fafc)", + "target": "app.main_content", + "items": [ + { + "name": "pipeline_core", + "label": "产线管理", + "icon": "{{entire_url('/imgs/cubes.svg')}}", + "url": "{{entire_url('/pipeline_core/')}}" + }, + { + "name": "pipeline_ops", + "label": "运营管理", + "icon": "{{entire_url('/imgs/dashboard.svg')}}", + "url": "{{entire_url('/pipeline_ops/')}}" + }, + { + "name": "pipeline_dist", + "label": "分销管理", + "icon": "{{entire_url('/imgs/truck.svg')}}", + "url": "{{entire_url('/pipeline_dist/')}}" + }, + { + "name": "pipeline_task", + "label": "任务中心", + "icon": "{{entire_url('/imgs/rocket.svg')}}", + "url": "{{entire_url('/pipeline_task/')}}" + }, + { + "name": "showcase", + "label": "展示平台", + "icon": "{{entire_url('/imgs/user-circle.svg')}}", + "url": "{{entire_url('/showcase/')}}" + } + ] + } + }, + { + "widgettype": "VScrollPanel", + "id": "main_content", + "options": { + "css": "filler", "width": "100%", - "padding": "32px", - "spacing": "24px" + "height": "100%" }, "subwidgets": [ { - "widgettype": "Title2", + "widgettype": "VBox", "options": { - "text": "产线平台" - } - }, - { - "widgettype": "Text", - "options": { - "text": "管理产线定义、运营配置、分销渠道与任务执行", - "cfontsize": 1.2 - } - }, - { - "widgettype": "ResponsableBox", - "options": { - "gap": "16px", - "minWidth": "220px" + "width": "100%", + "padding": "32px", + "spacing": "24px" }, "subwidgets": [ { - "widgettype": "Button", + "widgettype": "Title2", "options": { - "label": "产线管理", - "cwidth": 23, - "cheight": 8, - "css": "card" - }, - "binds": [{ - "wid": "self", - "event": "click", - "actiontype": "urlwidget", - "target": "-main_content", - "options": { - "url": "{{entire_url('/pipeline_core/')}}" - }, - "mode": "replace" - }] + "text": "产线平台" + } }, { - "widgettype": "Button", + "widgettype": "Text", "options": { - "label": "运营管理", - "cwidth": 23, - "cheight": 8, - "css": "card" - }, - "binds": [{ - "wid": "self", - "event": "click", - "actiontype": "urlwidget", - "target": "-main_content", - "options": { - "url": "{{entire_url('/pipeline_ops/')}}" - }, - "mode": "replace" - }] - }, - { - "widgettype": "Button", - "options": { - "label": "分销管理", - "cwidth": 23, - "cheight": 8, - "css": "card" - }, - "binds": [{ - "wid": "self", - "event": "click", - "actiontype": "urlwidget", - "target": "-main_content", - "options": { - "url": "{{entire_url('/pipeline_dist/')}}" - }, - "mode": "replace" - }] - }, - { - "widgettype": "Button", - "options": { - "label": "任务中心", - "cwidth": 23, - "cheight": 8, - "css": "card" - }, - "binds": [{ - "wid": "self", - "event": "click", - "actiontype": "urlwidget", - "target": "-main_content", - "options": { - "url": "{{entire_url('/pipeline_task/')}}" - }, - "mode": "replace" - }] + "text": "管理产线定义、运营配置、分销渠道与任务执行", + "cfontsize": 1.2 + } } ] } diff --git a/wwwroot/pipeline_sdlc b/wwwroot/pipeline_sdlc new file mode 120000 index 0000000..e95caa6 --- /dev/null +++ b/wwwroot/pipeline_sdlc @@ -0,0 +1 @@ +/home/hermesai/repos/pipeline-sdlc/wwwroot \ No newline at end of file diff --git a/wwwroot/rbac b/wwwroot/rbac new file mode 120000 index 0000000..7151f8b --- /dev/null +++ b/wwwroot/rbac @@ -0,0 +1 @@ +/home/hermesai/repos/pipeline-app/pkgs/rbac/wwwroot \ No newline at end of file