diff --git a/scripts/load_path.py b/scripts/load_path.py
index c6284b7..69cf30d 100644
--- a/scripts/load_path.py
+++ b/scripts/load_path.py
@@ -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()
diff --git a/wwwroot/api/ops_dashboard_data.dspy b/wwwroot/api/ops_dashboard_data.dspy
new file mode 100644
index 0000000..2ddae22
--- /dev/null
+++ b/wwwroot/api/ops_dashboard_data.dspy
@@ -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)
+ }
diff --git a/wwwroot/index.ui b/wwwroot/index.ui
index 3f463eb..e3387a0 100644
--- a/wwwroot/index.ui
+++ b/wwwroot/index.ui
@@ -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": "", "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": "", "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": "", "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"}}
]
}
]