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