feat: UI pages, build.sh, deployment scripts, model uitype fixes
- Add SDLC dashboard, pipeline editor, ops center UI - build.sh: clone business modules to pkgs/, xls2ui CRUD, fix created_by - global_func.py: password_encode None-safe wrapper - pipeline_app.py: permission cache warmup removed - load_path.py: RBAC permission registration for all modules - scripts/merge_i18n.py: i18n merge tool - bin/init_perms.py, bin/init_data.py: init scripts - set_role_perm.py: single permission registration - Model uitype fields set for form editing - pipeline_core/pipeline_ops load_path.py scripts
This commit is contained in:
parent
3623d5725e
commit
3c9fdcf444
@ -1,13 +1,24 @@
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from ahserver.globalEnv import initEnv, password_encode as _password_encode
|
||||
|
||||
DBNAME = "pipeline"
|
||||
|
||||
|
||||
def password_encode(s):
|
||||
"""Wrapper to handle None input during login form load."""
|
||||
if s is None:
|
||||
return ''
|
||||
return _password_encode(s)
|
||||
|
||||
|
||||
def get_module_dbname(mname):
|
||||
"""All modules share the pipeline database."""
|
||||
return DBNAME
|
||||
if 'pipeline' in mname:
|
||||
return 'pipeline'
|
||||
return 'sage'
|
||||
|
||||
|
||||
def set_globalvariable():
|
||||
initEnv()
|
||||
g = ServerEnv()
|
||||
g.get_module_dbname = get_module_dbname
|
||||
g.password_encode = password_encode
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pipeline Application - 产线管理独立应用"""
|
||||
import os, sys
|
||||
import os, sys, asyncio
|
||||
|
||||
app_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
root_dir = os.path.dirname(app_dir)
|
||||
@ -50,6 +50,8 @@ def init():
|
||||
load_tenant()
|
||||
load_ktv_adapter()
|
||||
|
||||
# Permission cache warms up on first request
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
webapp(init)
|
||||
|
||||
152
bin/init_data.py
Normal file
152
bin/init_data.py
Normal file
@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Import initialization data for pipeline-app.
|
||||
Creates default step types, pipeline templates, and demo data.
|
||||
Run: source py3/bin/activate && python bin/init_data.py [--demo]
|
||||
"""
|
||||
import sys, os, asyncio, json
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_DIR = os.path.dirname(SCRIPT_DIR)
|
||||
sys.path.insert(0, os.path.join(ROOT_DIR, 'py3', 'lib', 'python3.10', 'site-packages'))
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.folderUtils import ProgramPath
|
||||
from appPublic.uniqueID import getID
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from ahserver.globalEnv import initEnv
|
||||
from appPublic.timeUtils import curDateString, timestampstr
|
||||
|
||||
# ---- Default step types ----
|
||||
STEP_TYPES = [
|
||||
# (step_type, display_name, category, is_interactive, description, form_schema)
|
||||
('ai_generate', 'AI代码生成', 'AI生成', '0', '由AI自动生成代码', '{}'),
|
||||
('ai_review', 'AI代码审查', 'AI审查', '0', '由AI自动审查代码质量', '{}'),
|
||||
('ai_test', 'AI测试生成', 'AI测试', '0', '由AI生成测试用例', '{}'),
|
||||
('human_review', '人工代码审查', '人工审核', '1', '人工审查代码变更',
|
||||
json.dumps({"fields":[{"name":"approved","label":"审批通过","uitype":"switch"},{"name":"comment","label":"审批意见","uitype":"text"}]})),
|
||||
('human_approval', '发布审批', '人工审批', '1', '人工审批发布请求',
|
||||
json.dumps({"fields":[{"name":"approved","label":"批准发布","uitype":"switch"},{"name":"version","label":"发布版本","uitype":"text"}]})),
|
||||
('deploy_staging', '部署到测试环境', '部署', '0', '自动部署到测试环境', '{}'),
|
||||
('deploy_production', '部署到生产环境', '部署', '1', '审批后部署到生产环境',
|
||||
json.dumps({"fields":[{"name":"confirm","label":"确认部署","uitype":"switch"}]})),
|
||||
('run_tests', '运行测试套件', '测试', '0', '自动运行测试套件', '{}'),
|
||||
('build_artifact', '构建制品', '构建', '0', '构建发布制品', '{}'),
|
||||
('notify_webhook', 'Webhook通知', '通知', '0', '发送Webhook通知', '{}'),
|
||||
]
|
||||
|
||||
# ---- Default pipeline (SDLC template) ----
|
||||
SDLC_PIPELINE = {
|
||||
'id': 'pipeline_sdlc_default',
|
||||
'name': '标准SDLC产线',
|
||||
'description': '软件开发生命周期标准流程:AI生成→审查→测试→部署',
|
||||
'pipeline_type': 'sdlc',
|
||||
'status': 'published',
|
||||
'version': '1.0',
|
||||
}
|
||||
|
||||
SDLC_STEPS = [
|
||||
('ai_generate', 'AI代码生成', 1),
|
||||
('ai_review', 'AI代码审查', 2),
|
||||
('run_tests', '自动测试', 3),
|
||||
('human_review', '人工代码审查', 4),
|
||||
('build_artifact', '构建制品', 5),
|
||||
('human_approval', '发布审批', 6),
|
||||
('deploy_production', '生产部署', 7),
|
||||
]
|
||||
|
||||
|
||||
async def init_data(demo=False):
|
||||
config_file = os.path.join(ROOT_DIR, 'conf', 'config.json')
|
||||
if not os.path.exists(config_file):
|
||||
config_file = os.path.join(ROOT_DIR, '..', 'conf', 'config.json')
|
||||
config = getConfig(config_file, NS={'workdir': ROOT_DIR, 'ProgramPath': ProgramPath()})
|
||||
DBPools(config.databases)
|
||||
initEnv()
|
||||
env = ServerEnv()
|
||||
env.get_module_dbname = lambda m: 'pipeline' if 'pipeline' in m else 'sage'
|
||||
|
||||
async with DBPools().sqlorContext('pipeline') as sor:
|
||||
# 1. Create step types
|
||||
print('Step types:')
|
||||
for stype, display, cat, interactive, desc, schema in STEP_TYPES:
|
||||
existing = await sor.sqlExe(
|
||||
"SELECT step_type FROM pipeline_step_types WHERE step_type=${st}$", {'st': stype})
|
||||
if existing:
|
||||
print(f' SKIP {stype} ({display})')
|
||||
continue
|
||||
await sor.C('pipeline_step_types', {
|
||||
'step_type': stype, 'display_name': display,
|
||||
'category': cat, 'is_interactive': interactive,
|
||||
'description': desc, 'form_schema': schema,
|
||||
'created_at': timestampstr()
|
||||
})
|
||||
print(f' ADD {stype} ({display})')
|
||||
|
||||
# 2. Create default SDLC pipeline
|
||||
print('\nPipeline:')
|
||||
existing = await sor.sqlExe(
|
||||
"SELECT id FROM pipelines WHERE id=${pid}$", {'pid': SDLC_PIPELINE['id']})
|
||||
if not existing:
|
||||
await sor.C('pipelines', {
|
||||
**SDLC_PIPELINE,
|
||||
'created_by': 'system',
|
||||
'created_at': timestampstr(),
|
||||
'updated_at': timestampstr()
|
||||
})
|
||||
print(f' ADD {SDLC_PIPELINE["name"]}')
|
||||
|
||||
# Create steps
|
||||
for stype, sname, order in SDLC_STEPS:
|
||||
await sor.C('pipeline_steps', {
|
||||
'id': getID(),
|
||||
'pipeline_id': SDLC_PIPELINE['id'],
|
||||
'step_order': order,
|
||||
'step_name': sname,
|
||||
'step_type': stype,
|
||||
'step_config': '{}',
|
||||
'timeout_seconds': 3600,
|
||||
'retry_count': 1,
|
||||
'created_at': timestampstr()
|
||||
})
|
||||
print(f' step {order}: {sname}')
|
||||
else:
|
||||
print(f' SKIP {SDLC_PIPELINE["name"]} (exists)')
|
||||
|
||||
# 3. Demo data
|
||||
if demo:
|
||||
print('\nDemo data:')
|
||||
await _create_demo(sor)
|
||||
|
||||
print('\nInitialization complete.')
|
||||
|
||||
|
||||
async def _create_demo(sor):
|
||||
"""Create demo project and iteration."""
|
||||
pid = getID()
|
||||
existing = await sor.sqlExe("SELECT id FROM sd_projects LIMIT 1", {})
|
||||
if existing:
|
||||
print(' SKIP (data exists)')
|
||||
return
|
||||
await sor.C('sd_projects', {
|
||||
'id': pid, 'name': 'Pipeline App Demo',
|
||||
'description': '产线平台演示项目',
|
||||
'project_type': 'web', 'status': 'active',
|
||||
'created_by': 'system', 'created_at': timestampstr()
|
||||
})
|
||||
print(f' ADD project: Pipeline App Demo')
|
||||
|
||||
iid = getID()
|
||||
await sor.C('sd_iterations', {
|
||||
'id': iid, 'name': 'Sprint 1 - 基础框架',
|
||||
'project_id': pid, 'status': 'in_progress',
|
||||
'start_date': curDateString(), 'created_at': timestampstr()
|
||||
})
|
||||
print(f' ADD iteration: Sprint 1')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
demo_mode = '--demo' in sys.argv
|
||||
asyncio.run(init_data(demo=demo_mode))
|
||||
61
bin/init_perms.py
Normal file
61
bin/init_perms.py
Normal file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Initialize RBAC permissions for pipeline modules."""
|
||||
import sys, os, asyncio
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_DIR = os.path.dirname(SCRIPT_DIR)
|
||||
sys.path.insert(0, os.path.join(ROOT_DIR, 'py3', 'lib', 'python3.10', 'site-packages'))
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.folderUtils import ProgramPath
|
||||
from appPublic.uniqueID import getID
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from ahserver.globalEnv import initEnv
|
||||
|
||||
PERMS = [
|
||||
('/pipeline_core/', '产线管理访问', 'pipeline_core', 'logined'),
|
||||
('/pipeline-sdlc/', '开发产线访问', 'pipeline_sdlc', 'logined'),
|
||||
('/pipeline_ops/', '运营管理访问', 'pipeline_ops', 'logined'),
|
||||
('/pipeline_dist/', '分销管理访问', 'pipeline_dist', 'logined'),
|
||||
('/pipeline_task/', '任务中心访问', 'pipeline_task', 'logined'),
|
||||
('/showcase/', '展示平台访问', 'showcase', 'guest'),
|
||||
('/', '应用首页', 'app', 'guest'),
|
||||
]
|
||||
|
||||
async def init_perms():
|
||||
config = getConfig(ROOT_DIR, NS={'workdir': ROOT_DIR, 'ProgramPath': ProgramPath()})
|
||||
DBPools(config.databases)
|
||||
initEnv()
|
||||
env = ServerEnv()
|
||||
env.get_module_dbname = lambda m: 'sage'
|
||||
|
||||
async with DBPools().sqlorContext('sage') as sor:
|
||||
for path, name, perm_type, role_name in PERMS:
|
||||
# Build safe SQL with repr
|
||||
sql = "SELECT id FROM permission WHERE path=" + repr(path) + " LIMIT 1"
|
||||
existing = await sor.sqlExe(sql, {})
|
||||
if existing:
|
||||
print('SKIP {:<8s} {}'.format(role_name, path))
|
||||
continue
|
||||
|
||||
pid = getID()
|
||||
await sor.C('permission', {
|
||||
'id': pid, 'name': name, 'path': path,
|
||||
'permtype': perm_type
|
||||
})
|
||||
role_sql = "SELECT id FROM role WHERE name=" + repr(role_name) + " LIMIT 1"
|
||||
role_recs = await sor.sqlExe(role_sql, {})
|
||||
if role_recs:
|
||||
await sor.C('rolepermission', {
|
||||
'id': getID(),
|
||||
'roleid': role_recs[0].id,
|
||||
'permid': pid
|
||||
})
|
||||
print('ADD {:<8s} {}'.format(role_name, path))
|
||||
|
||||
print('\nInitialized {} permissions'.format(len(PERMS)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(init_perms())
|
||||
70
build.sh
70
build.sh
@ -11,7 +11,7 @@ if [ ! -d py3 ]; then
|
||||
fi
|
||||
source py3/bin/activate
|
||||
|
||||
# 2. Install foundation packages
|
||||
# 2. Install foundation packages (clone to pkgs)
|
||||
mkdir -p pkgs
|
||||
for m in apppublic sqlor ahserver bricks-for-python xls2ddl rbac appbase; do
|
||||
echo "install $m ..."
|
||||
@ -33,56 +33,50 @@ if [ ! -d bricks ]; then
|
||||
fi
|
||||
if [ -d bricks/bricks ]; then
|
||||
cd bricks/bricks && bash build.sh 2>&1 | tail -3
|
||||
ln -sf "$cdir/pkgs/bricks/dist" "$cdir/bricks"
|
||||
fi
|
||||
ln -sf "$cdir/pkgs/bricks/dist" "$cdir/bricks" 2>/dev/null || true
|
||||
cd "$cdir"
|
||||
|
||||
# 4. Clone pipeline-sdlc and showcase
|
||||
for m in pipeline-sdlc showcase; do
|
||||
echo "clone $m ..."
|
||||
# 4. Move local business modules into pkgs/
|
||||
for mod in pipeline_core pipeline_ops pipeline_dist; do
|
||||
if [ -d "$mod" ]; then
|
||||
mv "$mod" "pkgs/$mod"
|
||||
fi
|
||||
done
|
||||
|
||||
# 5. Install business modules (clone external, local already in pkgs)
|
||||
for mod in pipeline-sdlc showcase evaluate pipeline-service pipeline-task; do
|
||||
cd "$cdir/pkgs"
|
||||
if [ ! -d "$m" ]; then
|
||||
git clone https://git.opencomputing.cn/yumoqing/$m || echo "SKIP: $m clone failed"
|
||||
fi
|
||||
# pipeline-sdlc goes to root (same as other business modules)
|
||||
if [ "$m" = "pipeline-sdlc" ] && [ ! -d "$cdir/pipeline-sdlc" ]; then
|
||||
ln -sf "$cdir/pkgs/pipeline-sdlc" "$cdir/pipeline-sdlc"
|
||||
if [ ! -d "$mod" ]; then
|
||||
git clone https://git.opencomputing.cn/yumoqing/$mod || echo "SKIP: $mod"
|
||||
fi
|
||||
cd "$cdir"
|
||||
done
|
||||
|
||||
# 5. Install business modules
|
||||
for mod in pipeline_core pipeline_ops pipeline_dist pipeline-sdlc; do
|
||||
echo "install $mod ..."
|
||||
cd "$cdir/$mod"
|
||||
"$cdir/py3/bin/pip" install . 2>&1 | tail -1
|
||||
|
||||
# Generate DDL from models
|
||||
if [ -d models ] && ls models/*.json >/dev/null 2>&1; then
|
||||
"$cdir/py3/bin/json2ddl" mysql models/ > "$cdir/$mod/mysql.ddl.sql" 2>/dev/null || echo " DDL generation skipped (json2ddl not available)"
|
||||
# 6. pip install all modules from pkgs/
|
||||
for mod in pipeline_core pipeline_ops pipeline_dist pipeline-sdlc showcase; do
|
||||
if [ -d "pkgs/$mod" ]; then
|
||||
"$cdir/py3/bin/pip" install "pkgs/$mod/" 2>&1 | tail -1
|
||||
fi
|
||||
|
||||
# Generate CRUD UI from json definitions
|
||||
if [ -d json ] && ls json/*.json >/dev/null 2>&1; then
|
||||
cd json
|
||||
for f in *.json; do
|
||||
"$cdir/py3/bin/xls2ui" -m ../models -o ../wwwroot "$mod" "$f" 2>/dev/null || echo " CRUD generation skipped for $f"
|
||||
done
|
||||
cd ..
|
||||
fi
|
||||
cd "$cdir"
|
||||
done
|
||||
|
||||
# 5b. Install showcase module
|
||||
if [ -d "$cdir/pkgs/showcase" ]; then
|
||||
echo "install showcase ..."
|
||||
cd "$cdir/pkgs/showcase"
|
||||
"$cdir/py3/bin/pip" install . 2>&1 | tail -1
|
||||
# 7. SDLC pipeline-sdlc: regenerate CRUD from json
|
||||
if [ -d "pkgs/pipeline-sdlc/json" ]; then
|
||||
cd "pkgs/pipeline-sdlc/json"
|
||||
"$cdir/py3/bin/xls2ui" -m ../models -o ../wwwroot pipeline-sdlc *.json 2>&1 | grep -c 'handle' | xargs -I{} echo " xls2ui: {} tables handled"
|
||||
cd "$cdir"
|
||||
fi
|
||||
|
||||
# 6. Create runtime dirs
|
||||
mkdir -p "$cdir/logs" "$cdir/files"
|
||||
# 8. Create runtime dirs
|
||||
mkdir -p "$cdir/logs" "$cdir/files" "$cdir/conf"
|
||||
# 6. Fix auto-assign created_by in CRUD dspys (xls2ui overwritten)
|
||||
for d in sd_projects sd_iterations; do
|
||||
for t in add update; do
|
||||
f="$cdir/wwwroot/pipeline-sdlc/$d/${t}_${d}.dspy"
|
||||
[ -f "$f" ] && grep -q 'created_by' "$f" || sed -i "s/ns\['org_id'\] = userorgid/ns['org_id'] = userorgid\nns['created_by'] = userorgid/" "$f"
|
||||
done
|
||||
done
|
||||
|
||||
chmod +x "$cdir/start.sh" "$cdir/stop.sh"
|
||||
|
||||
chmod +x "$cdir/start.sh" "$cdir/stop.sh" 2>/dev/null || true
|
||||
echo "=== Build complete ==="
|
||||
|
||||
18
pipeline_core/json/llm.json
Normal file
18
pipeline_core/json/llm.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"tblname": "llm",
|
||||
"title": "大语言模型管理",
|
||||
"params": {
|
||||
"sortby": "created_at",
|
||||
"browserfields": {
|
||||
"exclouded": ["id", "api_key"],
|
||||
"cwidth": {}
|
||||
},
|
||||
"editexclouded": ["id", "created_at", "updated_at"],
|
||||
"editable": {
|
||||
"new_data_url": "{{entire_url('../api/llm_create.dspy')}}",
|
||||
"update_data_url": "{{entire_url('../api/llm_update.dspy')}}",
|
||||
"delete_data_url": "{{entire_url('../api/llm_delete.dspy')}}"
|
||||
},
|
||||
"confidential_fields": ["api_key"]
|
||||
}
|
||||
}
|
||||
25
pipeline_core/models/llm.json
Normal file
25
pipeline_core/models/llm.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "llm",
|
||||
"title": "大语言模型表",
|
||||
"primary": ["id"]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{"name": "id", "title": "主键", "type": "str", "length": 32, "nullable": "no"},
|
||||
{"name": "name", "title": "模型名称", "type": "str", "length": 100, "nullable": "no"},
|
||||
{"name": "provider", "title": "供应商", "type": "str", "length": 50, "nullable": "no"},
|
||||
{"name": "model_id", "title": "模型标识", "type": "str", "length": 100},
|
||||
{"name": "api_base", "title": "API地址", "type": "str", "length": 500},
|
||||
{"name": "api_key", "title": "API密钥", "type": "str", "length": 500},
|
||||
{"name": "max_tokens", "title": "最大Token", "type": "int", "default": "8192"},
|
||||
{"name": "status", "title": "状态", "type": "str", "length": 20, "default": "active"},
|
||||
{"name": "description", "title": "描述", "type": "text"},
|
||||
{"name": "created_at", "title": "创建时间", "type": "timestamp"},
|
||||
{"name": "updated_at", "title": "更新时间", "type": "timestamp"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "idx_llm_status", "idxtype": "index", "idxfields": ["status"]}
|
||||
]
|
||||
}
|
||||
@ -1,110 +1,122 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pipeline_steps",
|
||||
"title": "产线步骤表",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_steps_pipeline",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"pipeline_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_steps_order",
|
||||
"idxtype": "unique",
|
||||
"idxfields": [
|
||||
"pipeline_id",
|
||||
"step_order"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"field": "pipeline_id",
|
||||
"table": "pipelines",
|
||||
"valuefield": "id",
|
||||
"textfield": "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
"summary": [
|
||||
{
|
||||
"name": "pipeline_steps",
|
||||
"title": "产线步骤表",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"title": "id",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no",
|
||||
"uitype": "hidden"
|
||||
},
|
||||
{
|
||||
"name": "pipeline_id",
|
||||
"title": "所属产线",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no",
|
||||
"uitype": "select"
|
||||
},
|
||||
{
|
||||
"name": "step_order",
|
||||
"title": "步骤序号",
|
||||
"type": "int",
|
||||
"nullable": "no",
|
||||
"uitype": "number"
|
||||
},
|
||||
{
|
||||
"name": "step_name",
|
||||
"title": "步骤名称",
|
||||
"type": "str",
|
||||
"length": 100,
|
||||
"nullable": "no",
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "step_type",
|
||||
"title": "步骤类型",
|
||||
"type": "str",
|
||||
"length": 50,
|
||||
"nullable": "no",
|
||||
"uitype": "select"
|
||||
},
|
||||
{
|
||||
"name": "model_name",
|
||||
"title": "调用模型名称",
|
||||
"type": "str",
|
||||
"length": 100,
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "step_config",
|
||||
"title": "步骤配置JSON",
|
||||
"type": "text",
|
||||
"uitype": "textarea"
|
||||
},
|
||||
{
|
||||
"name": "input_schema",
|
||||
"title": "输入定义JSON",
|
||||
"type": "text",
|
||||
"uitype": "textarea"
|
||||
},
|
||||
{
|
||||
"name": "output_schema",
|
||||
"title": "输出定义JSON",
|
||||
"type": "text",
|
||||
"uitype": "textarea"
|
||||
},
|
||||
{
|
||||
"name": "timeout_seconds",
|
||||
"title": "超时秒数",
|
||||
"type": "int",
|
||||
"default": "300",
|
||||
"uitype": "number"
|
||||
},
|
||||
{
|
||||
"name": "retry_count",
|
||||
"title": "重试次数",
|
||||
"type": "int",
|
||||
"default": "0",
|
||||
"uitype": "number"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"title": "创建时间",
|
||||
"type": "timestamp",
|
||||
"uitype": "timestamp"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_steps_pipeline",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"pipeline_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_steps_order",
|
||||
"idxtype": "unique",
|
||||
"idxfields": [
|
||||
"pipeline_id",
|
||||
"step_order"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"field": "pipeline_id",
|
||||
"table": "pipelines",
|
||||
"valuefield": "id",
|
||||
"textfield": "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,85 +1,94 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pipeline_versions",
|
||||
"title": "产线发布记录表",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_versions_pipeline",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"pipeline_id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"field": "pipeline_id",
|
||||
"table": "pipelines",
|
||||
"valuefield": "id",
|
||||
"textfield": "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
"summary": [
|
||||
{
|
||||
"name": "pipeline_versions",
|
||||
"title": "产线发布记录表",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"title": "id",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no",
|
||||
"uitype": "hidden"
|
||||
},
|
||||
{
|
||||
"name": "pipeline_id",
|
||||
"title": "产线ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no",
|
||||
"uitype": "hidden"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"title": "版本号",
|
||||
"type": "str",
|
||||
"length": 20,
|
||||
"nullable": "no",
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "publish_status",
|
||||
"title": "发布状态",
|
||||
"type": "str",
|
||||
"length": 20,
|
||||
"nullable": "no",
|
||||
"default": "pending",
|
||||
"uitype": "select"
|
||||
},
|
||||
{
|
||||
"name": "published_by",
|
||||
"title": "发布人",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "published_at",
|
||||
"title": "发布时间",
|
||||
"type": "timestamp",
|
||||
"uitype": "timestamp"
|
||||
},
|
||||
{
|
||||
"name": "changelog",
|
||||
"title": "变更说明",
|
||||
"type": "text",
|
||||
"uitype": "textarea"
|
||||
},
|
||||
{
|
||||
"name": "config_snapshot",
|
||||
"title": "配置快照JSON",
|
||||
"type": "text",
|
||||
"uitype": "textarea"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"title": "创建时间",
|
||||
"type": "timestamp",
|
||||
"uitype": "timestamp"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_versions_pipeline",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"pipeline_id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"field": "pipeline_id",
|
||||
"table": "pipelines",
|
||||
"valuefield": "id",
|
||||
"textfield": "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,126 +1,139 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "pipelines",
|
||||
"title": "产线定义表",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_pipelines_name",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_pipelines_status",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"status"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"field": "pipeline_type",
|
||||
"table": "appcodes_kv",
|
||||
"valuefield": "k",
|
||||
"textfield": "v",
|
||||
"cond": "parentid='pipeline_type'"
|
||||
},
|
||||
{
|
||||
"field": "status",
|
||||
"table": "appcodes_kv",
|
||||
"valuefield": "k",
|
||||
"textfield": "v",
|
||||
"cond": "parentid='pipeline_status'"
|
||||
}
|
||||
]
|
||||
"summary": [
|
||||
{
|
||||
"name": "pipelines",
|
||||
"title": "产线定义表",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"title": "id",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no",
|
||||
"uitype": "hidden"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"title": "产线名称",
|
||||
"type": "str",
|
||||
"length": 200,
|
||||
"nullable": "no",
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"title": "产线描述",
|
||||
"type": "text",
|
||||
"uitype": "textarea"
|
||||
},
|
||||
{
|
||||
"name": "pipeline_type",
|
||||
"title": "产线类型",
|
||||
"type": "str",
|
||||
"length": 50,
|
||||
"nullable": "no",
|
||||
"uitype": "select"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"title": "当前版本",
|
||||
"type": "str",
|
||||
"length": 20,
|
||||
"default": "1.0.0",
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"title": "状态",
|
||||
"type": "str",
|
||||
"length": 20,
|
||||
"nullable": "no",
|
||||
"default": "draft",
|
||||
"uitype": "select"
|
||||
},
|
||||
{
|
||||
"name": "pipeline_config",
|
||||
"title": "产线配置JSON",
|
||||
"type": "text",
|
||||
"uitype": "textarea"
|
||||
},
|
||||
{
|
||||
"name": "model_api_url",
|
||||
"title": "模型API地址",
|
||||
"type": "str",
|
||||
"length": 500,
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "model_api_key",
|
||||
"title": "模型API密钥",
|
||||
"type": "str",
|
||||
"length": 500,
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "org_id",
|
||||
"title": "所属机构ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"default": "0",
|
||||
"uitype": "text"
|
||||
},
|
||||
{
|
||||
"name": "created_by",
|
||||
"title": "创建人",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"uitype": "hidden"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"title": "创建时间",
|
||||
"type": "timestamp",
|
||||
"uitype": "timestamp"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"title": "更新时间",
|
||||
"type": "timestamp",
|
||||
"uitype": "timestamp"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_pipelines_name",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_pipelines_status",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"status"
|
||||
]
|
||||
}
|
||||
],
|
||||
"codes": [
|
||||
{
|
||||
"field": "pipeline_type",
|
||||
"table": "appcodes_kv",
|
||||
"valuefield": "k",
|
||||
"textfield": "v",
|
||||
"cond": "parentid='pipeline_type'"
|
||||
},
|
||||
{
|
||||
"field": "status",
|
||||
"table": "appcodes_kv",
|
||||
"valuefield": "k",
|
||||
"textfield": "v",
|
||||
"cond": "parentid='pipeline_status'"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -249,6 +249,54 @@ async def publish_pipeline(params_kw):
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
async def create_llm(params_kw):
|
||||
result = {'success': False, 'message': ''}
|
||||
try:
|
||||
db, dbname = _get_sor()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
data = params_kw.copy()
|
||||
data.pop('page', None)
|
||||
data.pop('rows', None)
|
||||
data.pop('data_filter', None)
|
||||
data['id'] = getID()
|
||||
await sor.C('llm', data)
|
||||
result['success'] = True
|
||||
result['message'] = '创建成功'
|
||||
except Exception as e:
|
||||
result['message'] = str(e)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
async def update_llm(params_kw):
|
||||
result = {'success': False, 'message': ''}
|
||||
try:
|
||||
db, dbname = _get_sor()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
data = params_kw.copy()
|
||||
data.pop('page', None)
|
||||
data.pop('rows', None)
|
||||
data.pop('data_filter', None)
|
||||
await sor.U('llm', data)
|
||||
result['success'] = True
|
||||
result['message'] = '更新成功'
|
||||
except Exception as e:
|
||||
result['message'] = str(e)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
async def delete_llm(params_kw):
|
||||
result = {'success': False, 'message': ''}
|
||||
try:
|
||||
db, dbname = _get_sor()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
await sor.D('llm', {'id': params_kw.get('id')})
|
||||
result['success'] = True
|
||||
result['message'] = '删除成功'
|
||||
except Exception as e:
|
||||
result['message'] = str(e)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
def load_pipeline_core():
|
||||
"""注册函数到 ServerEnv"""
|
||||
env = ServerEnv()
|
||||
@ -259,6 +307,10 @@ def load_pipeline_core():
|
||||
env.update_pipelines = update_pipeline
|
||||
env.delete_pipeline = delete_pipeline
|
||||
env.delete_pipelines = delete_pipeline
|
||||
# LLM
|
||||
env.create_llm = create_llm
|
||||
env.update_llm = update_llm
|
||||
env.delete_llm = delete_llm
|
||||
# Pipeline Steps
|
||||
env.create_pipeline_step = create_pipeline_step
|
||||
env.create_pipeline_steps = create_pipeline_step
|
||||
|
||||
@ -1,88 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
pipeline_core 模块 RBAC 权限管理脚本
|
||||
|
||||
使用方法:
|
||||
cd ~/repos/sage
|
||||
./py3/bin/python ~/test/pipeline-app/pipeline_core/scripts/load_path.py
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def find_sage_root():
|
||||
candidates = [
|
||||
os.path.expanduser("~/repos/sage"),
|
||||
os.path.expanduser("~/sage"),
|
||||
]
|
||||
for c in candidates:
|
||||
if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")):
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
SAGE_ROOT = find_sage_root()
|
||||
if not SAGE_ROOT:
|
||||
print("ERROR: Cannot find Sage root directory")
|
||||
sys.exit(1)
|
||||
|
||||
PYTHON = os.path.join(SAGE_ROOT, "py3", "bin", "python")
|
||||
SET_PERM_SCRIPT = os.path.join(SAGE_ROOT, "set_role_perm.py")
|
||||
"""RBAC path registration for pipeline_core module."""
|
||||
import os, sys, subprocess
|
||||
|
||||
MOD = "pipeline_core"
|
||||
|
||||
# ============================================================
|
||||
# 权限路径定义
|
||||
# ============================================================
|
||||
|
||||
# operator — 产线管理人员
|
||||
PATHS_OPERATOR = [
|
||||
f"/{MOD}",
|
||||
PATHS_ANY = [
|
||||
f"/{MOD}/index.ui",
|
||||
f"/{MOD}/pipelines/",
|
||||
f"/{MOD}/pipeline_steps/",
|
||||
f"/{MOD}/pipeline_versions/",
|
||||
f"/{MOD}/api/pipelines_create.dspy",
|
||||
f"/{MOD}/api/pipelines_update.dspy",
|
||||
f"/{MOD}/api/pipelines_delete.dspy",
|
||||
]
|
||||
|
||||
PATHS_LOGINED = [
|
||||
f"/{MOD}",
|
||||
# Pipelines CRUD
|
||||
f"/{MOD}/pipelines/index.ui",
|
||||
f"/{MOD}/pipelines/get_pipelines.dspy",
|
||||
f"/{MOD}/pipelines/add_pipelines.dspy",
|
||||
f"/{MOD}/pipelines/update_pipelines.dspy",
|
||||
f"/{MOD}/pipelines/delete_pipelines.dspy",
|
||||
# Pipeline Steps CRUD
|
||||
f"/{MOD}/pipeline_steps/index.ui",
|
||||
f"/{MOD}/pipeline_steps/get_pipeline_steps.dspy",
|
||||
f"/{MOD}/pipeline_steps/add_pipeline_steps.dspy",
|
||||
f"/{MOD}/pipeline_steps/update_pipeline_steps.dspy",
|
||||
f"/{MOD}/pipeline_steps/delete_pipeline_steps.dspy",
|
||||
# Pipeline Versions
|
||||
f"/{MOD}/pipeline_versions/index.ui",
|
||||
f"/{MOD}/pipeline_versions/get_pipeline_versions.dspy",
|
||||
f"/{MOD}/pipeline_versions/add_pipeline_versions.dspy",
|
||||
f"/{MOD}/pipeline_versions/update_pipeline_versions.dspy",
|
||||
f"/{MOD}/pipeline_versions/delete_pipeline_versions.dspy",
|
||||
# Pipeline Editor
|
||||
f"/{MOD}/pipeline_editor/index.ui",
|
||||
f"/{MOD}/pipeline_editor/save_steps.dspy",
|
||||
# Data sources
|
||||
f"/{MOD}/pipeline_steps/get_pipelines.dspy",
|
||||
f"/{MOD}/pipeline_steps/get_step_types.dspy",
|
||||
# API
|
||||
f"/{MOD}/api/pipeline_publish.dspy",
|
||||
f"/{MOD}/api/pipeline_steps_create.dspy",
|
||||
f"/{MOD}/api/pipeline_steps_update.dspy",
|
||||
f"/{MOD}/api/pipeline_steps_delete.dspy",
|
||||
f"/{MOD}/api/pipeline_versions_create.dspy",
|
||||
f"/{MOD}/api/pipeline_versions_update.dspy",
|
||||
f"/{MOD}/api/pipeline_versions_delete.dspy",
|
||||
f"/{MOD}/api/pipeline_publish.dspy",
|
||||
f"/{MOD}/api/pipelines_create.dspy",
|
||||
f"/{MOD}/api/pipelines_update.dspy",
|
||||
f"/{MOD}/api/pipelines_delete.dspy",
|
||||
]
|
||||
|
||||
# developer — 开发者(包含所有 operator 路径)
|
||||
PATHS_DEVELOPER = PATHS_OPERATOR[:]
|
||||
|
||||
|
||||
def run_set_perm(role, path):
|
||||
cmd = [PYTHON, SET_PERM_SCRIPT, role, path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
|
||||
|
||||
def register_role_paths(role, paths):
|
||||
count = 0
|
||||
for p in paths:
|
||||
if run_set_perm(role, p):
|
||||
count += 1
|
||||
print(f" {role}: {count}/{len(paths)} paths registered")
|
||||
return count
|
||||
|
||||
|
||||
def main():
|
||||
print(f"Sage root: {SAGE_ROOT}")
|
||||
print("Registering pipeline_core module RBAC permissions...")
|
||||
total = 0
|
||||
total += register_role_paths("operator", PATHS_OPERATOR)
|
||||
total += register_role_paths("developer", PATHS_DEVELOPER)
|
||||
print(f"\nDone. Total {total} permission entries registered.")
|
||||
print("NOTE: Restart Sage after permission changes to reload RBAC cache.")
|
||||
root = os.path.expanduser("~/pipeline")
|
||||
if not os.path.isfile(os.path.join(root, "set_role_perm.py")):
|
||||
print("ERROR: pipeline root not found")
|
||||
sys.exit(1)
|
||||
set_perm = os.path.join(root, "set_role_perm.py")
|
||||
py = sys.executable
|
||||
count = 0
|
||||
for role, paths in [("any", PATHS_ANY), ("logined", PATHS_LOGINED)]:
|
||||
for path in paths:
|
||||
r = subprocess.run([py, set_perm, role, path], capture_output=True, text=True, cwd=root)
|
||||
if r.returncode == 0:
|
||||
count += 1
|
||||
else:
|
||||
print(f" WARN: {path} -> {r.stderr.strip()[-80:]}")
|
||||
print(f"Registered {count}/{len(PATHS_ANY)+len(PATHS_LOGINED)} paths for {MOD}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -1,68 +1,254 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "height": "100%", "padding": "0"},
|
||||
"subwidgets": [
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "0"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"padding": "24px 24px 12px 24px",
|
||||
"cheight": 6
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"width": "100%", "alignItems": "center", "marginBottom": "20px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title2", "options": {"text": "产线管理"}},
|
||||
{"widgettype": "Filler"},
|
||||
{"widgettype": "Text", "options": {"text": "产线定义、步骤配置与发布管理", "cfontsize": 1.2}}
|
||||
]
|
||||
"widgettype": "Title2",
|
||||
"options": {
|
||||
"text": "产线管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "filler", "spacing": 16},
|
||||
"subwidgets": [
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "产线定义、步骤配置与发布管理",
|
||||
"cfontsize": 1.2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "12px 24px 12px 24px",
|
||||
"cheight": 18
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {
|
||||
"gap": "24px",
|
||||
"minWidth": "260px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "EDITOR CARD HERE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"cwidth": 23,
|
||||
"cheight": 12,
|
||||
"padding": "24px",
|
||||
"cursor": "pointer",
|
||||
"bgcolor": "#fff"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {"gap": "24px", "minWidth": "260px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_core_content", "options": {"url": "{{entire_url('pipelines/')}}"}, "mode": "replace"}],
|
||||
"subwidgets": [
|
||||
{"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#EFF6FF", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
|
||||
{"widgettype": "Svg", "options": {"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\"><path d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"/></svg>", "width": "24px", "height": "24px"}}
|
||||
]},
|
||||
{"widgettype": "Title3", "options": {"text": "产线定义", "marginBottom": "4px"}},
|
||||
{"widgettype": "Text", "options": {"text": "管理产线基本信息与配置", "cfontsize": 1.2}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_core_content", "options": {"url": "{{entire_url('pipeline_steps/')}}"}, "mode": "replace"}],
|
||||
"subwidgets": [
|
||||
{"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#F0FDF4", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
|
||||
{"widgettype": "Svg", "options": {"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22C55E\" stroke-width=\"2\"><path d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"/></svg>", "width": "24px", "height": "24px"}}
|
||||
]},
|
||||
{"widgettype": "Title3", "options": {"text": "产线步骤", "marginBottom": "4px"}},
|
||||
{"widgettype": "Text", "options": {"text": "配置产线执行步骤与参数", "cfontsize": 1.2}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 12, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.pipeline_core_content", "options": {"url": "{{entire_url('pipeline_versions/')}}"}, "mode": "replace"}],
|
||||
"subwidgets": [
|
||||
{"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#FFFBEB", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
|
||||
{"widgettype": "Svg", "options": {"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F59E0B\" stroke-width=\"2\"><path d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg>", "width": "24px", "height": "24px"}}
|
||||
]},
|
||||
{"widgettype": "Title3", "options": {"text": "发布记录", "marginBottom": "4px"}},
|
||||
{"widgettype": "Text", "options": {"text": "查看产线版本发布历史", "cfontsize": 1.2}}
|
||||
]
|
||||
}
|
||||
]
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.main_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/pipeline_core/pipelines/')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "44px",
|
||||
"height": "44px",
|
||||
"bgcolor": "#EFF6FF",
|
||||
"borderRadius": "12px",
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"marginBottom": "12px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\"><path d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"id": "pipeline_core_content",
|
||||
"options": {"css": "filler", "width": "100%", "height": "100%"}
|
||||
"widgettype": "Title3",
|
||||
"options": {
|
||||
"text": "产线定义",
|
||||
"marginBottom": "4px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "管理产线基本信息与配置",
|
||||
"cfontsize": 1.2
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"cwidth": 23,
|
||||
"cheight": 12,
|
||||
"padding": "24px",
|
||||
"cursor": "pointer",
|
||||
"bgcolor": "#fff"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.main_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/pipeline_core/pipeline_steps/')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "44px",
|
||||
"height": "44px",
|
||||
"bgcolor": "#F0FDF4",
|
||||
"borderRadius": "12px",
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"marginBottom": "12px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22C55E\" stroke-width=\"2\"><path d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Title3",
|
||||
"options": {
|
||||
"text": "产线步骤",
|
||||
"marginBottom": "4px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "配置产线执行步骤与参数",
|
||||
"cfontsize": 1.2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"cwidth": 23,
|
||||
"cheight": 12,
|
||||
"padding": "24px",
|
||||
"cursor": "pointer",
|
||||
"bgcolor": "#fff"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.main_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/pipeline_core/pipeline_versions/')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "44px",
|
||||
"height": "44px",
|
||||
"bgcolor": "#FFFBEB",
|
||||
"borderRadius": "12px",
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"marginBottom": "12px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#F59E0B\" stroke-width=\"2\"><path d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Title3",
|
||||
"options": {
|
||||
"text": "发布记录",
|
||||
"marginBottom": "4px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "查看产线版本发布历史",
|
||||
"cfontsize": 1.2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"id": "pipeline_core_content",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
85
pipeline_core/wwwroot/pipeline_editor/index.ui
Normal file
85
pipeline_core/wwwroot/pipeline_editor/index.ui
Normal file
@ -0,0 +1,85 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "height": "100%", "padding": "24px", "gap": "20px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"alignItems": "center", "gap": "16px", "padding": "0 0 16px 0", "borderBottom": "1px solid #334155"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title2", "options": {"text": "产线编排画布"}},
|
||||
{"widgettype": "Filler"},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "发布版本", "bgcolor": "#6366f1", "color": "#fff"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "method", "target": "app.pipeline_editor", "method": "publish"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"gap": "12px", "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "选择产线:", "cfontsize": 1, "color": "#e2e8f0"}},
|
||||
{
|
||||
"widgettype": "DataSelector",
|
||||
"id": "ds_pipelines",
|
||||
"options": {
|
||||
"data_url": "{{entire_url('/pipeline_core/pipeline_steps/get_pipelines.dspy')}}",
|
||||
"valueField": "id",
|
||||
"textField": "name",
|
||||
"width": "300px",
|
||||
"placeholder": "选择要编辑的产线..."
|
||||
},
|
||||
"binds": [{"wid": "self", "event": "change", "actiontype": "method", "target": "app.pipeline_editor", "method": "load_steps"}]
|
||||
},
|
||||
{
|
||||
"widgettype": "DataSelector",
|
||||
"id": "ds_step_types",
|
||||
"options": {
|
||||
"data_url": "{{entire_url('/pipeline_core/pipeline_steps/get_step_types.dspy')}}",
|
||||
"valueField": "step_type",
|
||||
"textField": "display_name",
|
||||
"width": "240px",
|
||||
"placeholder": "添加步骤类型..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "+ 添加步骤", "bgcolor": "#4ade80", "color": "#000"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "method", "target": "app.pipeline_editor", "method": "add_step"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "DataViewer",
|
||||
"id": "dv_steps",
|
||||
"options": {
|
||||
"data_url": "",
|
||||
"width": "100%",
|
||||
"columns": ["id", "step_order", "step_name", "step_type"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "steps_canvas",
|
||||
"options": {"width": "100%", "gap": "8px", "padding": "0", "minHeight": "300px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {"text": "选择产线后,步骤将在此显示。可以拖拽排序、点击编辑。", "cfontsize": 1, "color": "#64748b", "padding": "40px", "alignItems": "center"}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"gap": "12px", "justifyContent": "flex-end", "padding": "16px 0 0 0", "borderTop": "1px solid #334155"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {"label": "保存排序", "bgcolor": "#3b82f6", "color": "#fff"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "method", "target": "app.pipeline_editor", "method": "save_order"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
47
pipeline_core/wwwroot/pipeline_editor/save_steps.dspy
Normal file
47
pipeline_core/wwwroot/pipeline_editor/save_steps.dspy
Normal file
@ -0,0 +1,47 @@
|
||||
"""
|
||||
Pipeline Editor: Save step order after reordering
|
||||
POST params: pipeline_id, steps (JSON array of {id, step_order})
|
||||
"""
|
||||
from sqlor.dbpools import DBPools
|
||||
|
||||
async def main():
|
||||
userid = await get_user()
|
||||
if not userid:
|
||||
return {'error': 'unauthorized'}
|
||||
|
||||
pipeline_id = params_kw.pipeline_id
|
||||
steps = params_kw.steps # JSON array
|
||||
|
||||
if not pipeline_id or not steps:
|
||||
return {'error': 'missing pipeline_id or steps'}
|
||||
|
||||
if isinstance(steps, str):
|
||||
import json
|
||||
steps = json.loads(steps)
|
||||
|
||||
dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
for i, step in enumerate(steps):
|
||||
await sor.U('pipeline_steps', {
|
||||
'id': step['id'],
|
||||
'step_order': i + 1,
|
||||
'step_name': step.get('step_name', ''),
|
||||
'step_type': step.get('step_type', ''),
|
||||
'step_config': step.get('step_config', '{}'),
|
||||
'timeout_seconds': step.get('timeout_seconds', 300),
|
||||
'retry_count': step.get('retry_count', 0)
|
||||
})
|
||||
|
||||
# Create version record
|
||||
from appPublic.uniqueID import getID
|
||||
vid = getID()
|
||||
await sor.C('pipeline_versions', {
|
||||
'id': vid,
|
||||
'pipeline_id': pipeline_id,
|
||||
'version': f"v{len(steps)}.0",
|
||||
'publish_status': 'draft',
|
||||
'changelog': 'Editor save',
|
||||
'config_snapshot': json.dumps(steps, ensure_ascii=False) if 'json' in dir() else str(steps)
|
||||
})
|
||||
|
||||
return {'success': True, 'version_id': vid, 'step_count': len(steps)}
|
||||
@ -1,130 +1,14 @@
|
||||
|
||||
ns = params_kw.copy()
|
||||
from sqlor.dbpools import DBPools
|
||||
|
||||
|
||||
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":[]
|
||||
}
|
||||
async def main():
|
||||
pipeline_id = params_kw.pipeline_id or params_kw.id
|
||||
dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
if pipeline_id:
|
||||
recs = await sor.R('pipeline_steps', {'pipeline_id': pipeline_id})
|
||||
else:
|
||||
recs = await sor.R('pipeline_steps', {})
|
||||
# Sort by step_order
|
||||
recs = sorted(recs, key=lambda r: r.step_order if hasattr(r,'step_order') else 0)
|
||||
return recs
|
||||
|
||||
13
pipeline_core/wwwroot/pipeline_steps/get_pipelines.dspy
Normal file
13
pipeline_core/wwwroot/pipeline_steps/get_pipelines.dspy
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
from sqlor.dbpools import DBPools
|
||||
|
||||
async def main():
|
||||
dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
recs = await sor.R('pipelines', {'status': 'published'})
|
||||
if not recs:
|
||||
recs = await sor.R('pipelines', {})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({'id': r.id, 'name': r.name, 'text': r.name, 'value': r.id})
|
||||
return result
|
||||
18
pipeline_core/wwwroot/pipeline_steps/get_step_types.dspy
Normal file
18
pipeline_core/wwwroot/pipeline_steps/get_step_types.dspy
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
from sqlor.dbpools import DBPools
|
||||
|
||||
async def main():
|
||||
dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
recs = await sor.R('pipeline_step_types', {})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'step_type': r.step_type,
|
||||
'display_name': r.display_name,
|
||||
'text': f"{r.display_name} ({r.step_type})",
|
||||
'value': r.step_type,
|
||||
'category': r.category,
|
||||
'is_interactive': r.is_interactive
|
||||
})
|
||||
return result
|
||||
@ -1,22 +1,38 @@
|
||||
"""Register RBAC paths for pipeline_dist module."""
|
||||
#!/usr/bin/env python3
|
||||
"""RBAC path registration for pipeline_dist + showcase modules."""
|
||||
import os, sys, subprocess
|
||||
|
||||
RBAC_PATHS = [
|
||||
'/api/distributors_list.dspy',
|
||||
'/api/distributors_get.dspy',
|
||||
'/api/distributors_save.dspy',
|
||||
'/api/distributors_delete.dspy',
|
||||
'/api/distributor_pipeline_list.dspy',
|
||||
'/api/distributor_pipeline_get.dspy',
|
||||
'/api/distributor_pipeline_save.dspy',
|
||||
'/api/distributor_pipeline_delete.dspy',
|
||||
'/api/distributor_generate_key.dspy',
|
||||
MODS = [
|
||||
("pipeline_dist", [
|
||||
"/pipeline_dist",
|
||||
"/pipeline_dist/index.ui",
|
||||
"/pipeline_dist/distributors/index.ui",
|
||||
"/pipeline_dist/distributor_pipeline/index.ui",
|
||||
]),
|
||||
("showcase", [
|
||||
"/showcase",
|
||||
"/showcase/index.ui",
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
def load_paths(register):
|
||||
"""Register all RBAC paths for this module."""
|
||||
for path in RBAC_PATHS:
|
||||
register(f'pipeline_dist{path}', {
|
||||
'module': 'pipeline_dist',
|
||||
'path': path,
|
||||
})
|
||||
def main():
|
||||
root = os.path.expanduser("~/pipeline")
|
||||
set_perm = os.path.join(root, "set_role_perm.py")
|
||||
if not os.path.isfile(set_perm):
|
||||
print("ERROR: pipeline root not found"); sys.exit(1)
|
||||
py = sys.executable
|
||||
count = 0
|
||||
total = 0
|
||||
for mod, paths in MODS:
|
||||
total += len(paths)
|
||||
for path in paths:
|
||||
r = subprocess.run([py, set_perm, "logined", path], capture_output=True, text=True, cwd=root)
|
||||
if r.returncode == 0: count += 1
|
||||
else: print(f" WARN: {path}")
|
||||
print(f" {mod}: {len(paths)} paths")
|
||||
print(f"Registered {count}/{total}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -1,90 +1,62 @@
|
||||
"""Register all RBAC paths for pipeline_ops module"""
|
||||
#!/usr/bin/env python3
|
||||
"""RBAC path registration for pipeline_ops module."""
|
||||
import os, sys, subprocess
|
||||
|
||||
MOD = "pipeline_ops"
|
||||
|
||||
PATHS_ANY = [
|
||||
f"/{MOD}/index.ui",
|
||||
]
|
||||
|
||||
PATHS_LOGINED = [
|
||||
f"/{MOD}",
|
||||
f"/{MOD}/api/ops_dashboard_data.dspy",
|
||||
# Pricing
|
||||
f"/{MOD}/pipeline_pricing/index.ui",
|
||||
f"/{MOD}/pipeline_pricing/get_pipeline_pricing.dspy",
|
||||
f"/{MOD}/pipeline_pricing/add_pipeline_pricing.dspy",
|
||||
f"/{MOD}/pipeline_pricing/update_pipeline_pricing.dspy",
|
||||
f"/{MOD}/pipeline_pricing/delete_pipeline_pricing.dspy",
|
||||
# Capacity
|
||||
f"/{MOD}/pipeline_capacity/index.ui",
|
||||
f"/{MOD}/pipeline_capacity/get_pipeline_capacity.dspy",
|
||||
f"/{MOD}/pipeline_capacity/add_pipeline_capacity.dspy",
|
||||
f"/{MOD}/pipeline_capacity/update_pipeline_capacity.dspy",
|
||||
f"/{MOD}/pipeline_capacity/delete_pipeline_capacity.dspy",
|
||||
# Usage Log
|
||||
f"/{MOD}/pipeline_usage_log/index.ui",
|
||||
f"/{MOD}/pipeline_usage_log/get_pipeline_usage_log.dspy",
|
||||
f"/{MOD}/pipeline_usage_log/add_pipeline_usage_log.dspy",
|
||||
f"/{MOD}/pipeline_usage_log/update_pipeline_usage_log.dspy",
|
||||
f"/{MOD}/pipeline_usage_log/delete_pipeline_usage_log.dspy",
|
||||
# API
|
||||
f"/{MOD}/api/pipeline_pricing_create.dspy",
|
||||
f"/{MOD}/api/pipeline_pricing_update.dspy",
|
||||
f"/{MOD}/api/pipeline_pricing_delete.dspy",
|
||||
f"/{MOD}/api/pipeline_capacity_create.dspy",
|
||||
f"/{MOD}/api/pipeline_capacity_update.dspy",
|
||||
f"/{MOD}/api/pipeline_capacity_delete.dspy",
|
||||
f"/{MOD}/api/pipeline_usage_log_create.dspy",
|
||||
f"/{MOD}/api/pipeline_usage_log_update.dspy",
|
||||
f"/{MOD}/api/pipeline_usage_log_delete.dspy",
|
||||
f"/{MOD}/api/get_search_pipeline_id.dspy",
|
||||
]
|
||||
|
||||
|
||||
def load_paths():
|
||||
"""Return list of RBAC paths for this module"""
|
||||
paths = [
|
||||
{
|
||||
"path": "/pipeline_ops/",
|
||||
"name": "产线运营",
|
||||
"icon": "settings",
|
||||
"parent": "",
|
||||
"sort": 30
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/pipeline_pricing",
|
||||
"name": "定价管理",
|
||||
"icon": "price-tag",
|
||||
"parent": "/pipeline_ops/",
|
||||
"sort": 31
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_pricing_create.dspy",
|
||||
"name": "新增定价",
|
||||
"parent": "/pipeline_ops/pipeline_pricing",
|
||||
"sort": 32
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_pricing_update.dspy",
|
||||
"name": "修改定价",
|
||||
"parent": "/pipeline_ops/pipeline_pricing",
|
||||
"sort": 33
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_pricing_delete.dspy",
|
||||
"name": "删除定价",
|
||||
"parent": "/pipeline_ops/pipeline_pricing",
|
||||
"sort": 34
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/pipeline_capacity",
|
||||
"name": "供应量管理",
|
||||
"icon": "gauge",
|
||||
"parent": "/pipeline_ops/",
|
||||
"sort": 40
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_capacity_create.dspy",
|
||||
"name": "新增供应量配置",
|
||||
"parent": "/pipeline_ops/pipeline_capacity",
|
||||
"sort": 41
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_capacity_update.dspy",
|
||||
"name": "修改供应量配置",
|
||||
"parent": "/pipeline_ops/pipeline_capacity",
|
||||
"sort": 42
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_capacity_delete.dspy",
|
||||
"name": "删除供应量配置",
|
||||
"parent": "/pipeline_ops/pipeline_capacity",
|
||||
"sort": 43
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/pipeline_usage_log",
|
||||
"name": "使用记录",
|
||||
"icon": "list",
|
||||
"parent": "/pipeline_ops/",
|
||||
"sort": 50
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_usage_log_create.dspy",
|
||||
"name": "新增使用记录",
|
||||
"parent": "/pipeline_ops/pipeline_usage_log",
|
||||
"sort": 51
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_usage_log_update.dspy",
|
||||
"name": "修改使用记录",
|
||||
"parent": "/pipeline_ops/pipeline_usage_log",
|
||||
"sort": 52
|
||||
},
|
||||
{
|
||||
"path": "/pipeline_ops/api/pipeline_usage_log_delete.dspy",
|
||||
"name": "删除使用记录",
|
||||
"parent": "/pipeline_ops/pipeline_usage_log",
|
||||
"sort": 53
|
||||
}
|
||||
]
|
||||
return paths
|
||||
def main():
|
||||
root = os.path.expanduser("~/pipeline")
|
||||
set_perm = os.path.join(root, "set_role_perm.py")
|
||||
if not os.path.isfile(set_perm):
|
||||
print("ERROR: pipeline root not found"); sys.exit(1)
|
||||
py = sys.executable
|
||||
count = 0
|
||||
for role, paths in [("any", PATHS_ANY), ("logined", PATHS_LOGINED)]:
|
||||
for path in paths:
|
||||
r = subprocess.run([py, set_perm, role, path], capture_output=True, text=True, cwd=root)
|
||||
if r.returncode == 0: count += 1
|
||||
else: print(f" WARN: {path}")
|
||||
print(f"Registered {count}/{len(PATHS_ANY)+len(PATHS_LOGINED)} paths for {MOD}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
61
pipeline_ops/wwwroot/api/ops_dashboard_data.dspy
Normal file
61
pipeline_ops/wwwroot/api/ops_dashboard_data.dspy
Normal file
@ -0,0 +1,61 @@
|
||||
"""
|
||||
Operations Dashboard Data: task queue, human tasks, stats
|
||||
"""
|
||||
from sqlor.dbpools import DBPools
|
||||
|
||||
async def main():
|
||||
dbname = get_module_dbname('pipeline_core')
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
# Task stats by state
|
||||
task_stats = await sor.sqlExe("""
|
||||
SELECT state, COUNT(*) as cnt FROM pipeline_tasks
|
||||
GROUP BY state ORDER BY cnt DESC
|
||||
""", {})
|
||||
|
||||
stats = {'submitted':0, 'running':0, 'completed':0, 'failed':0, 'cancelled':0}
|
||||
for r in task_stats:
|
||||
stats[r.state] = r.cnt
|
||||
|
||||
# Recent tasks (last 20)
|
||||
tasks = await sor.sqlExe("""
|
||||
SELECT t.id, t.title, t.state, t.pipeline_id, t.created_at
|
||||
FROM pipeline_tasks t
|
||||
ORDER BY t.created_at DESC LIMIT 20
|
||||
""", {})
|
||||
task_list = []
|
||||
for r in tasks:
|
||||
task_list.append({
|
||||
'id': r.id, 'title': r.title, 'state': r.state,
|
||||
'pipeline_id': r.pipeline_id, 'created_at': str(r.created_at)[:19]
|
||||
})
|
||||
|
||||
# Pending human tasks
|
||||
human_tasks = await sor.sqlExe("""
|
||||
SELECT h.id, h.task_id, h.task_type, h.step_name, h.status,
|
||||
h.assignee_role, h.created_at, h.expired_at,
|
||||
t.title as task_title
|
||||
FROM pipeline_human_tasks h
|
||||
LEFT JOIN pipeline_tasks t ON h.task_id = t.id
|
||||
WHERE h.status IN ('pending', 'in_progress')
|
||||
ORDER BY h.created_at DESC LIMIT 20
|
||||
""", {})
|
||||
|
||||
human_list = []
|
||||
for r in human_tasks:
|
||||
human_list.append({
|
||||
'id': r.id, 'task_id': r.task_id, 'task_type': r.task_type,
|
||||
'step_name': r.step_name, 'status': r.status,
|
||||
'assignee_role': r.assignee_role, 'task_title': r.task_title,
|
||||
'created_at': str(r.created_at)[:19],
|
||||
'expired_at': str(r.expired_at)[:19] if r.expired_at else None
|
||||
})
|
||||
|
||||
return {
|
||||
'stats': stats,
|
||||
'tasks': task_list,
|
||||
'human_tasks': human_list,
|
||||
'total_tasks': sum(stats.values()),
|
||||
'active_tasks': stats['running'],
|
||||
'pending_reviews': len(human_list)
|
||||
}
|
||||
@ -1,46 +1,120 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "height": "100%", "padding": "24px", "spacing": "20px"},
|
||||
"options": {"width": "100%", "height": "100%", "padding": "24px", "gap": "20px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title2", "options": {"text": "产线运营"}},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"alignItems": "center", "gap": "12px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title2", "options": {"text": "产线运营中心"}},
|
||||
{"widgettype": "Text", "options": {"text": "任务队列、审批管理、执行监控", "cfontsize": 1.1, "color": "#888"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {"gap": "24px", "minWidth": "260px"},
|
||||
"options": {"gap": "16px", "minWidth": "150px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 11, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.main_content", "options": {"url": "{{entire_url('pipeline_pricing/')}}"}, "mode": "replace"}],
|
||||
"options": {"bgcolor": "#1e293b", "padding": "20px", "borderRadius": "12px", "gap": "4px", "border": "1px solid #334155", "cwidth": 16, "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2, "color": "#60a5fa", "fontWeight": "bold"}},
|
||||
{"widgettype": "Text", "options": {"text": "已提交", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"bgcolor": "#1e293b", "padding": "20px", "borderRadius": "12px", "gap": "4px", "border": "1px solid #334155", "cwidth": 16, "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2, "color": "#fbbf24", "fontWeight": "bold"}},
|
||||
{"widgettype": "Text", "options": {"text": "运行中", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"bgcolor": "#1e293b", "padding": "20px", "borderRadius": "12px", "gap": "4px", "border": "1px solid #334155", "cwidth": 16, "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2, "color": "#4ade80", "fontWeight": "bold"}},
|
||||
{"widgettype": "Text", "options": {"text": "已完成", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"bgcolor": "#1e293b", "padding": "20px", "borderRadius": "12px", "gap": "4px", "border": "1px solid #334155", "cwidth": 16, "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2, "color": "#f87171", "fontWeight": "bold"}},
|
||||
{"widgettype": "Text", "options": {"text": "失败", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"bgcolor": "#1e293b", "padding": "20px", "borderRadius": "12px", "gap": "4px", "border": "1px solid #334155", "cwidth": 16, "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2, "color": "#a78bfa", "fontWeight": "bold"}},
|
||||
{"widgettype": "Text", "options": {"text": "待审批", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"bgcolor": "#1e293b", "padding": "20px", "borderRadius": "12px", "gap": "4px", "border": "1px solid #334155", "cwidth": 16, "alignItems": "center"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": "-", "cfontsize": 2, "color": "#e2e8f0", "fontWeight": "bold"}},
|
||||
{"widgettype": "Text", "options": {"text": "总任务", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {"gap": "16px", "minWidth": "400px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"bgcolor": "#1e293b", "padding": "20px", "borderRadius": "12px", "gap": "12px", "border": "1px solid #334155", "cwidth": 60},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title3", "options": {"text": "近期任务"}},
|
||||
{"widgettype": "Text", "options": {"text": "暂无任务,提交产线任务后将在此显示", "cfontsize": 0.9, "color": "#64748b", "padding": "20px 0"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"bgcolor": "#1e293b", "padding": "20px", "borderRadius": "12px", "gap": "12px", "border": "1px solid #334155", "cwidth": 40},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title3", "options": {"text": "待审批队列"}},
|
||||
{"widgettype": "Text", "options": {"text": "暂无审批任务", "cfontsize": 0.9, "color": "#64748b", "padding": "20px 0"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {"gap": "16px", "minWidth": "180px"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 10, "padding": "24px", "cursor": "pointer", "bgcolor": "#1e293b", "border": "1px solid #334155"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.main_content", "options": {"url": "{{entire_url('/pipeline_ops/pipeline_pricing/')}}"}, "mode": "replace"}],
|
||||
"subwidgets": [
|
||||
{"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#FEF2F2", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
|
||||
{"widgettype": "Svg", "options": {"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#EF4444\" stroke-width=\"2\"><path d=\"M12 2a10 10 0 100 20 10 10 0 000-20z\"/><path d=\"M12 6v6l4 2\"/></svg>", "width": "24px", "height": "24px"}}
|
||||
]},
|
||||
{"widgettype": "Title3", "options": {"text": "定价管理", "marginBottom": "4px"}},
|
||||
{"widgettype": "Text", "options": {"text": "管理产线计费方式和价格", "cfontsize": 0.9}}
|
||||
{"widgettype": "Text", "options": {"text": "管理产线计费方式和价格", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 11, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.main_content", "options": {"url": "{{entire_url('pipeline_capacity/')}}"}, "mode": "replace"}],
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 10, "padding": "24px", "cursor": "pointer", "bgcolor": "#1e293b", "border": "1px solid #334155"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.main_content", "options": {"url": "{{entire_url('/pipeline_ops/pipeline_capacity/')}}"}, "mode": "replace"}],
|
||||
"subwidgets": [
|
||||
{"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#F0F9FF", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
|
||||
{"widgettype": "Svg", "options": {"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#0EA5E9\" stroke-width=\"2\"><path d=\"M13 10V3L4 14h7v7l9-11h-7z\"/></svg>", "width": "24px", "height": "24px"}}
|
||||
]},
|
||||
{"widgettype": "Title3", "options": {"text": "供应量管理", "marginBottom": "4px"}},
|
||||
{"widgettype": "Text", "options": {"text": "配置产线并发和调用限额", "cfontsize": 0.9}}
|
||||
{"widgettype": "Text", "options": {"text": "配置产线并发和调用限额", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 11, "padding": "24px", "cursor": "pointer", "bgcolor": "#fff"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.main_content", "options": {"url": "{{entire_url('pipeline_usage_log/')}}"}, "mode": "replace"}],
|
||||
"options": {"css": "card", "cwidth": 23, "cheight": 10, "padding": "24px", "cursor": "pointer", "bgcolor": "#1e293b", "border": "1px solid #334155"},
|
||||
"binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.main_content", "options": {"url": "{{entire_url('/pipeline_ops/pipeline_usage_log/')}}"}, "mode": "replace"}],
|
||||
"subwidgets": [
|
||||
{"widgettype": "VBox", "options": {"width": "44px", "height": "44px", "bgcolor": "#F5F3FF", "borderRadius": "12px", "alignItems": "center", "justifyContent": "center", "marginBottom": "12px"}, "subwidgets": [
|
||||
{"widgettype": "Svg", "options": {"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#8B5CF6\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/></svg>", "width": "24px", "height": "24px"}}
|
||||
]},
|
||||
{"widgettype": "Title3", "options": {"text": "使用记录", "marginBottom": "4px"}},
|
||||
{"widgettype": "Text", "options": {"text": "查看产线调用和消费记录", "cfontsize": 0.9}}
|
||||
{"widgettype": "Text", "options": {"text": "查看产线调用和消费记录", "cfontsize": 0.9, "color": "#94a3b8"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
102
scripts/load_path.py
Normal file
102
scripts/load_path.py
Normal file
@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Register all pipeline module RBAC permissions in one run."""
|
||||
import sys, os, asyncio
|
||||
|
||||
ROOT = os.path.expanduser("~/pipeline")
|
||||
sys.path.insert(0, os.path.join(ROOT, "py3", "lib", "python3.10", "site-packages"))
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.folderUtils import ProgramPath
|
||||
from appPublic.uniqueID import getID
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from ahserver.globalEnv import initEnv
|
||||
|
||||
PERMS = [
|
||||
# pipeline-sdlc
|
||||
("/pipeline-sdlc", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_dashboard/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_projects/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_project_list/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_iterations/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_iteration_list/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_test_plans/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_test_plan_list/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_test_cases/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_test_case_list/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_bugs/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_bug_list/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_deploy_envs/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_deploy_env_list/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/sd_review/*", "pipeline_sdlc", "logined"),
|
||||
("/pipeline-sdlc/api/*", "pipeline_sdlc", "logined"),
|
||||
# pipeline_core
|
||||
("/pipeline_core", "pipeline_core", "logined"),
|
||||
("/pipeline_core/pipelines/*", "pipeline_core", "logined"),
|
||||
("/pipeline_core/pipeline_steps/*", "pipeline_core", "logined"),
|
||||
("/pipeline_core/pipeline_versions/*", "pipeline_core", "logined"),
|
||||
("/pipeline_core/pipeline_editor/*", "pipeline_core", "logined"),
|
||||
("/pipeline_core/api/*", "pipeline_core", "logined"),
|
||||
# pipeline_ops
|
||||
("/pipeline_ops", "pipeline_ops", "logined"),
|
||||
("/pipeline_ops/api/*", "pipeline_ops", "logined"),
|
||||
("/pipeline_ops/pipeline_pricing/*", "pipeline_ops", "logined"),
|
||||
("/pipeline_ops/pipeline_capacity/*", "pipeline_ops", "logined"),
|
||||
("/pipeline_ops/pipeline_usage_log/*", "pipeline_ops", "logined"),
|
||||
# pipeline_dist
|
||||
("/pipeline_dist", "pipeline_dist", "logined"),
|
||||
("/pipeline_dist/*", "pipeline_dist", "logined"),
|
||||
# showcase
|
||||
("/showcase", "showcase", "logined"),
|
||||
("/showcase/*", "showcase", "logined"),
|
||||
# app root (any)
|
||||
("/**", "app", "any"),
|
||||
("/index.ui", "app", "any"),
|
||||
("/pipeline-sdlc/index.ui", "pipeline_sdlc", "any"),
|
||||
("/pipeline_core/index.ui", "pipeline_core", "any"),
|
||||
("/pipeline_ops/index.ui", "pipeline_ops", "any"),
|
||||
("/pipeline_dist/index.ui", "pipeline_dist", "any"),
|
||||
]
|
||||
|
||||
|
||||
async def register():
|
||||
config = getConfig(ROOT, NS={"workdir": ROOT, "ProgramPath": ProgramPath()})
|
||||
DBPools(config.databases)
|
||||
initEnv()
|
||||
env = ServerEnv()
|
||||
env.get_module_dbname = lambda m: "sage"
|
||||
|
||||
async with DBPools().sqlorContext("sage") as sor:
|
||||
total = 0
|
||||
for path, module, role_name in PERMS:
|
||||
sql = "SELECT id FROM permission WHERE path=" + repr(path) + " LIMIT 1"
|
||||
existing = await sor.sqlExe(sql, {})
|
||||
if existing:
|
||||
continue
|
||||
|
||||
pid = getID()
|
||||
await sor.C("permission", {
|
||||
"id": pid, "name": module + ":" + path,
|
||||
"path": path, "permtype": module
|
||||
})
|
||||
role_sql = "SELECT id FROM role WHERE name=" + repr(role_name) + " LIMIT 1"
|
||||
role_recs = await sor.sqlExe(role_sql, {})
|
||||
if role_recs:
|
||||
await sor.C("rolepermission", {
|
||||
"id": getID(),
|
||||
"roleid": role_recs[0].id,
|
||||
"permid": pid
|
||||
})
|
||||
total += 1
|
||||
|
||||
print(f"Registered {total} new permissions")
|
||||
# Verify
|
||||
async with DBPools().sqlorContext("sage") as sor:
|
||||
cnt = await sor.sqlExe(
|
||||
"SELECT COUNT(*) as n FROM permission WHERE path LIKE '/pipeline%' OR path LIKE '/showcase%'", {})
|
||||
print(f"Total pipeline permissions: {cnt[0].n}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(register())
|
||||
99
scripts/merge_i18n.py
Normal file
99
scripts/merge_i18n.py
Normal file
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Merge i18n translations from all pipeline modules into wwwroot/i18n.
|
||||
Scans each module's i18n directory and merges into the app's centralized i18n.
|
||||
|
||||
Usage: python scripts/merge_i18n.py
|
||||
"""
|
||||
import os, json, sys
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_DIR = os.path.dirname(SCRIPT_DIR)
|
||||
WWWROOT = os.path.join(ROOT_DIR, 'wwwroot')
|
||||
I18N_OUT = os.path.join(WWWROOT, 'i18n')
|
||||
|
||||
# Modules to scan for i18n
|
||||
MODULES = [
|
||||
('pipeline_core', os.path.join(ROOT_DIR, 'pipeline_core', 'i18n')),
|
||||
('pipeline_ops', os.path.join(ROOT_DIR, 'pipeline_ops', 'i18n')),
|
||||
('pipeline_dist', os.path.join(ROOT_DIR, 'pipeline_dist', 'i18n')),
|
||||
('pipeline-sdlc', os.path.join(os.path.dirname(ROOT_DIR), 'pipeline-sdlc', 'i18n')),
|
||||
('showcase', os.path.join(os.path.dirname(ROOT_DIR), 'showcase', 'i18n')),
|
||||
]
|
||||
|
||||
LANGS = ['zh', 'en', 'ko', 'jp']
|
||||
|
||||
|
||||
def parse_msg_txt(filepath):
|
||||
"""Parse msg.txt: key=value format."""
|
||||
result = OrderedDict()
|
||||
if not os.path.exists(filepath):
|
||||
return result
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if '=' in line:
|
||||
key, val = line.split('=', 1)
|
||||
result[key.strip()] = val.strip()
|
||||
return result
|
||||
|
||||
|
||||
def merge():
|
||||
os.makedirs(I18N_OUT, exist_ok=True)
|
||||
stats = {}
|
||||
|
||||
for lang in LANGS:
|
||||
merged = OrderedDict()
|
||||
out_dir = os.path.join(I18N_OUT, lang)
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Load existing merged translations
|
||||
i18n_file = os.path.join(out_dir, 'i18n.json')
|
||||
if os.path.exists(i18n_file):
|
||||
with open(i18n_file, 'r', encoding='utf-8') as f:
|
||||
merged = json.load(f, object_pairs_hook=OrderedDict)
|
||||
|
||||
# Scan each module
|
||||
for mod_name, mod_i18n_dir in MODULES:
|
||||
lang_dir = os.path.join(mod_i18n_dir, lang)
|
||||
if not os.path.isdir(lang_dir):
|
||||
# Try app's internal i18n
|
||||
alt_dir = os.path.join(ROOT_DIR, 'pipeline_core', 'i18n', lang)
|
||||
if not os.path.isdir(alt_dir):
|
||||
continue
|
||||
lang_dir = alt_dir
|
||||
|
||||
# Load msg.txt
|
||||
msg_file = os.path.join(lang_dir, 'msg.txt')
|
||||
if os.path.exists(msg_file):
|
||||
msgs = parse_msg_txt(msg_file)
|
||||
for k, v in msgs.items():
|
||||
if k not in merged:
|
||||
merged[k] = v
|
||||
stats.setdefault(mod_name, 0)
|
||||
stats[mod_name] += 1
|
||||
|
||||
# Load i18n.json
|
||||
mod_i18n = os.path.join(lang_dir, 'i18n.json')
|
||||
if os.path.exists(mod_i18n):
|
||||
with open(mod_i18n, 'r', encoding='utf-8') as f:
|
||||
extra = json.load(f, object_pairs_hook=OrderedDict)
|
||||
for k, v in extra.items():
|
||||
if k not in merged:
|
||||
merged[k] = v
|
||||
|
||||
# Write merged
|
||||
with open(i18n_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(merged, f, ensure_ascii=False, indent=2)
|
||||
print(f' {lang}: {len(merged)} keys')
|
||||
|
||||
# Write msg.txt for the app root
|
||||
print(f'\nMerged i18n for {len(MODULES)} modules -> {I18N_OUT}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
merge()
|
||||
@ -1,52 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Wrapper script for RBAC permission registration.
|
||||
Called by modules' load_path.py scripts.
|
||||
Usage: py3/bin/python set_role_perm.py <role> <path>
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import asyncio
|
||||
Register a single RBAC permission for pipeline modules.
|
||||
Called by init_perms.py or directly.
|
||||
|
||||
# Add current directory to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
Usage:
|
||||
python set_role_perm.py <role> <path>
|
||||
python set_role_perm.py admin /pipeline_core/pipelines/*.dspy
|
||||
"""
|
||||
import sys, os, asyncio
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_DIR = os.path.dirname(SCRIPT_DIR) if os.path.basename(SCRIPT_DIR) == 'bin' else SCRIPT_DIR
|
||||
sys.path.insert(0, os.path.join(ROOT_DIR, 'py3', 'lib', 'python3.10', 'site-packages'))
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
|
||||
from rbac.set_role_perms import set_role_perm as _set_role_perm
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.folderUtils import ProgramPath
|
||||
from appPublic.uniqueID import getID
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from ahserver.globalEnv import initEnv
|
||||
|
||||
|
||||
async def main(role, path):
|
||||
"""Register a single permission path for a role."""
|
||||
# Determine module name from path
|
||||
# e.g., /pipeline_core/index.ui -> pipeline_core
|
||||
config = getConfig(os.path.join(ROOT_DIR, 'conf', 'config.json'),
|
||||
NS={'workdir': ROOT_DIR, 'ProgramPath': ProgramPath()})
|
||||
DBPools(config.databases)
|
||||
initEnv()
|
||||
env = ServerEnv()
|
||||
env.get_module_dbname = lambda m: 'pipeline' if 'pipeline' in m else 'sage'
|
||||
|
||||
# Determine module from path
|
||||
parts = path.strip('/').split('/')
|
||||
module = parts[0] if parts else 'app'
|
||||
|
||||
# Get database name from config
|
||||
config = getConfig('.', {'workdir': '.'})
|
||||
db = DBPools(config.databases)
|
||||
|
||||
# Call the actual permission setter
|
||||
await _set_role_perm('pipeline', module, '*', role, path)
|
||||
|
||||
async with DBPools().sqlorContext('sage') as sor:
|
||||
existing = await sor.sqlExe(
|
||||
"SELECT id FROM role_path WHERE module=${m}$ AND path=${p}$ AND role_name=${r}$",
|
||||
{'m': module, 'p': path, 'r': role})
|
||||
if existing:
|
||||
print(f'Permission exists: {role} {path}')
|
||||
return
|
||||
|
||||
def run(coro):
|
||||
"""Run async function in event loop."""
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop.run_until_complete(coro)
|
||||
finally:
|
||||
loop.close()
|
||||
await sor.C('role_path', {
|
||||
'id': getID(),
|
||||
'module': module,
|
||||
'path': path,
|
||||
'role_name': role,
|
||||
'description': f'Auto-registered: {role} -> {path}'
|
||||
})
|
||||
print(f'Registered: {role} -> {path}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
print(f"Usage: {sys.argv[0]} <role> <path>")
|
||||
print(f"Example: {sys.argv[0]} logined /pipeline_core/index.ui")
|
||||
print(f" roles: guest, logined, admin")
|
||||
print(f" path example: /pipeline_core/index.ui")
|
||||
sys.exit(1)
|
||||
|
||||
role = sys.argv[1]
|
||||
path = sys.argv[2]
|
||||
|
||||
run(main(role, path))
|
||||
|
||||
asyncio.run(main(sys.argv[1], sys.argv[2]))
|
||||
|
||||
222
wwwroot/index.ui
222
wwwroot/index.ui
@ -1,73 +1,167 @@
|
||||
{
|
||||
"id": "app",
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "height": "100%", "padding": "0"},
|
||||
"subwidgets": [
|
||||
"id": "app",
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"padding": "0"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"cheight": 2,
|
||||
"bgcolor": "#1e293b",
|
||||
"padding": "0 16px",
|
||||
"alignItems": "center",
|
||||
"gap": "12px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"width": "100%", "cheight": 2, "bgcolor": "#1e293b", "padding": "0 16px", "alignItems": "center", "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": {"text": "产线平台", "color": "#fff"}},
|
||||
{"widgettype": "Filler"},
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"url": "{{entire_url('/rbac/user/user_panel.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
"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": "HBox",
|
||||
"options": {"css": "filler", "padding": "0", "gap": "0"},
|
||||
"subwidgets": [
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "产线平台",
|
||||
"color": "#fff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"padding": "0",
|
||||
"gap": "0"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "220px",
|
||||
"minWidth": "200px",
|
||||
"height": "100%",
|
||||
"bgcolor": "#f8fafc"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Menu",
|
||||
"id": "sidebar_menu",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"target": "app.main_content",
|
||||
"items": [
|
||||
{
|
||||
"name": "pipeline_core",
|
||||
"label": "产线管理",
|
||||
"icon": "{{entire_url('/imgs/cubes.svg')}}",
|
||||
"url": "{{entire_url('/pipeline_core/')}}"
|
||||
},
|
||||
{
|
||||
"name": "pipeline_sdlc",
|
||||
"label": "开发产线",
|
||||
"icon": "{{entire_url('/imgs/workflow.svg')}}",
|
||||
"url": "{{entire_url('/pipeline_sdlc/')}}"
|
||||
},
|
||||
{
|
||||
"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": "pipeline_ktv",
|
||||
"label": "KTV产线",
|
||||
"icon": "{{entire_url('/imgs/mic.svg')}}",
|
||||
"url": "{{entire_url('/pipeline_task/task_submit.ui')}}"
|
||||
},
|
||||
{
|
||||
"name": "showcase",
|
||||
"label": "展示平台",
|
||||
"icon": "{{entire_url('/imgs/user-circle.svg')}}",
|
||||
"url": "{{entire_url('/showcase/')}}"
|
||||
},
|
||||
{
|
||||
"name": "tenant",
|
||||
"label": "租户管理",
|
||||
"icon": "{{entire_url('/imgs/globe.svg')}}",
|
||||
"url": "{{entire_url('/tenant/')}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"id": "main_content",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "32px",
|
||||
"spacing": "24px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options":{
|
||||
"width": "220px",
|
||||
"minWidth": "200px",
|
||||
"height": "100%",
|
||||
"bgcolor": "#f8fafc"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype": "Menu",
|
||||
"id": "sidebar_menu",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"target": "app.main_content",
|
||||
"items": [
|
||||
{"name": "pipeline_core", "label": "产线管理", "icon": "{{entire_url('/imgs/cubes.svg')}}", "url": "{{entire_url('/pipeline_core/')}}"},
|
||||
{"name": "pipeline_sdlc", "label": "开发产线", "icon": "{{entire_url('/imgs/workflow.svg')}}", "url": "{{entire_url('/pipeline_sdlc/')}}"},
|
||||
{"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": "pipeline_ktv", "label": "KTV产线", "icon": "{{entire_url('/imgs/mic.svg')}}", "url": "{{entire_url('/pipeline_task/task_submit.ui')}}"},
|
||||
{"name": "showcase", "label": "展示平台", "icon": "{{entire_url('/imgs/user-circle.svg')}}", "url": "{{entire_url('/showcase/')}}"},
|
||||
{"name": "tenant", "label": "租户管理", "icon": "{{entire_url('/imgs/globe.svg')}}", "url": "{{entire_url('/tenant/')}}"}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
"widgettype": "Title2",
|
||||
"options": {
|
||||
"text": "产线平台"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"id": "main_content",
|
||||
"options": {"css": "filler", "width": "100%", "height": "100%"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%", "padding": "32px", "spacing": "24px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title2", "options": {"text": "产线平台"}},
|
||||
{"widgettype": "Text", "options": {"text": "管理产线定义、运营配置、分销渠道与任务执行", "cfontsize": 1.2}}
|
||||
]
|
||||
}
|
||||
]
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "管理产线定义、运营配置、分销渠道与任务执行",
|
||||
"cfontsize": 1.2
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
5
wwwroot/pipeline_core/llm/create_llm.dspy
Normal file
5
wwwroot/pipeline_core/llm/create_llm.dspy
Normal file
@ -0,0 +1,5 @@
|
||||
func = create_llm
|
||||
if func is None:
|
||||
return json.dumps({'status': 'error', 'message': 'function not found'})
|
||||
result = await func(params_kw)
|
||||
return result
|
||||
5
wwwroot/pipeline_core/llm/delete_llm.dspy
Normal file
5
wwwroot/pipeline_core/llm/delete_llm.dspy
Normal file
@ -0,0 +1,5 @@
|
||||
func = delete_llm
|
||||
if func is None:
|
||||
return json.dumps({'status': 'error', 'message': 'function not found'})
|
||||
result = await func(params_kw)
|
||||
return result
|
||||
11
wwwroot/pipeline_core/llm/get_llms.dspy
Normal file
11
wwwroot/pipeline_core/llm/get_llms.dspy
Normal file
@ -0,0 +1,11 @@
|
||||
from sqlor.dbpools import DBPools
|
||||
|
||||
async def main():
|
||||
dbname = get_module_dbname('pipeline_core')
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
recs = await sor.R('llm', {'status': 'active'})
|
||||
if not recs:
|
||||
recs = await sor.R('llm', {})
|
||||
if not recs:
|
||||
recs = []
|
||||
return recs
|
||||
43
wwwroot/pipeline_core/llm/index.ui
Normal file
43
wwwroot/pipeline_core/llm/index.ui
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"widgettype": "Tabular",
|
||||
"id": "llm_tbl",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"title": "大模型管理",
|
||||
"css": "card",
|
||||
"data_url": "{{entire_url('./get_llms.dspy')}}",
|
||||
"data_method": "GET",
|
||||
"editable": {
|
||||
"new_data_url": "{{entire_url('./create_llm.dspy')}}",
|
||||
"update_data_url": "{{entire_url('./update_llm.dspy')}}",
|
||||
"delete_data_url": "{{entire_url('./delete_llm.dspy')}}"
|
||||
},
|
||||
"browserfields": {
|
||||
"exclouded": ["id", "api_key"],
|
||||
"alters": {
|
||||
"status": {
|
||||
"uitype": "code",
|
||||
"data": [
|
||||
{"value": "active", "text": "启用"},
|
||||
{"value": "inactive", "text": "停用"}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"editexclouded": ["id", "created_at", "updated_at"],
|
||||
"confidential_fields": ["api_key"],
|
||||
"fields": [
|
||||
{"name": "id", "title": "ID", "type": "str", "cwidth": 0},
|
||||
{"name": "name", "title": "模型名称", "type": "str", "cwidth": 18},
|
||||
{"name": "provider", "title": "供应商", "type": "str", "cwidth": 12},
|
||||
{"name": "model_id", "title": "模型标识", "type": "str", "cwidth": 18},
|
||||
{"name": "api_base", "title": "API地址", "type": "str", "cwidth": 20},
|
||||
{"name": "api_key", "title": "API密钥", "type": "str", "cwidth": 14},
|
||||
{"name": "max_tokens", "title": "最大Token", "type": "str", "cwidth": 10},
|
||||
{"name": "status", "title": "状态", "type": "str", "cwidth": 8},
|
||||
{"name": "description", "title": "描述", "type": "str", "cwidth": 16},
|
||||
{"name": "created_at", "title": "创建时间", "type": "str", "cwidth": 14},
|
||||
{"name": "updated_at", "title": "更新时间", "type": "str", "cwidth": 14}
|
||||
]
|
||||
}
|
||||
}
|
||||
5
wwwroot/pipeline_core/llm/update_llm.dspy
Normal file
5
wwwroot/pipeline_core/llm/update_llm.dspy
Normal file
@ -0,0 +1,5 @@
|
||||
func = update_llm
|
||||
if func is None:
|
||||
return json.dumps({'status': 'error', 'message': 'function not found'})
|
||||
result = await func(params_kw)
|
||||
return result
|
||||
Loading…
x
Reference in New Issue
Block a user