diff --git a/build.sh b/build.sh
index 6f79d83..0a28b99 100644
--- a/build.sh
+++ b/build.sh
@@ -34,8 +34,8 @@ SAGE_MODULE_WWWROOT="$WWWROOT/$MODULE_NAME"
echo "Linking wwwroot..."
mkdir -p "$SAGE_MODULE_WWWROOT/api"
-# Link all .ui and .js files (must be at wwwroot root per bricks convention)
-for f in "$MODULE_WWWROOT"/*.ui "$MODULE_WWWROOT"/*.js; do
+# Link all .ui, .js, and .css files (must be at wwwroot root per bricks convention)
+for f in "$MODULE_WWWROOT"/*.ui "$MODULE_WWWROOT"/*.js "$MODULE_WWWROOT"/*.css; do
[ -f "$f" ] && ln -sf "$f" "$SAGE_MODULE_WWWROOT/"
done
diff --git a/dashboard_for_sage.egg-info/SOURCES.txt b/dashboard_for_sage.egg-info/SOURCES.txt
index fb4cf4e..e4c1c5d 100644
--- a/dashboard_for_sage.egg-info/SOURCES.txt
+++ b/dashboard_for_sage.egg-info/SOURCES.txt
@@ -2,6 +2,7 @@ README.md
pyproject.toml
dashboard_for_sage/__init__.py
dashboard_for_sage/init.py
+dashboard_for_sage/load_dashboard.py
dashboard_for_sage.egg-info/PKG-INFO
dashboard_for_sage.egg-info/SOURCES.txt
dashboard_for_sage.egg-info/dependency_links.txt
diff --git a/wwwroot/api/top_models.dspy b/wwwroot/api/top_models.dspy
new file mode 100644
index 0000000..e2f61d3
--- /dev/null
+++ b/wwwroot/api/top_models.dspy
@@ -0,0 +1,6 @@
+# coding=utf-8
+"""Top models data API for ChartBar"""
+import json
+
+models = await get_top_models(request)
+print(json.dumps(models))
diff --git a/wwwroot/chart_top_models.ui b/wwwroot/chart_top_models.ui
new file mode 100644
index 0000000..99148e1
--- /dev/null
+++ b/wwwroot/chart_top_models.ui
@@ -0,0 +1,10 @@
+{
+ "widgettype": "ChartBar",
+ "options": {
+ "height": "280px",
+ "width": "100%",
+ "data_url": "{{entire_url('api/top_models.dspy')}}",
+ "nameField": "model_name",
+ "valueFields": ["cnt", "total_amount"]
+ }
+}
diff --git a/wwwroot/global_menu.ui b/wwwroot/global_menu.ui
new file mode 100644
index 0000000..8c6eb97
--- /dev/null
+++ b/wwwroot/global_menu.ui
@@ -0,0 +1,58 @@
+{% set roles = get_user_roles(get_user()) %}
+{
+ "widgettype": "Menu",
+ "id": "global_nav_menu",
+ "options": {
+ "width": "100%",
+ "height": "100%",
+ "bgcolor": "#111827",
+ "items": [
+ {
+ "name": "dashboard",
+ "label": "仪表盘",
+ "icon": "fa fa-dashboard",
+ "url": "{{entire_url('index.ui')}}",
+ "target": "app.sage_main_content"
+ },
+{% if get_user() %}
+ {
+ "name": "llmage",
+ "label": "LLM 模型管理",
+ "icon": "fa fa-brain",
+ "submenu": "{{entire_url('/llmage/menu.ui')}}"
+ },
+ {
+ "name": "rag",
+ "label": "知识库管理",
+ "icon": "fa fa-database",
+ "url": "{{entire_url('/rag/menu.ui')}}",
+ "target": "app.sage_main_content"
+ },
+{% endif %}
+{% if 'reseller.operator' in roles or 'owner.superuser' in roles %}
+ {
+ "name": "platformbiz",
+ "label": "平台业务",
+ "icon": "fa fa-building",
+ "submenu": "{{entire_url('/platformbiz/menu.ui')}}"
+ },
+{% endif %}
+{% if get_user() %}
+ {
+ "name": "rbac",
+ "label": "用户与权限",
+ "icon": "fa fa-users",
+ "submenu": "{{entire_url('/rbac/admin_menu.ui')}}"
+ },
+{% endif %}
+ {
+ "name": "hermes_web_cli",
+ "label": "AI Agent",
+ "icon": "fa fa-robot",
+ "url": "{{entire_url('/hermes-web-cli/index.ui')}}",
+ "target": "app.sage_main_content"
+ }
+ ],
+ "menuitem_css": "menuitem"
+ }
+}
diff --git a/wwwroot/index.ui b/wwwroot/index.ui
index 0bcd771..b19f2af 100644
--- a/wwwroot/index.ui
+++ b/wwwroot/index.ui
@@ -2,97 +2,373 @@
"widgettype": "VBox",
"options": {
"width": "100%",
- "height": "100%",
- "padding": "20px",
- "bgcolor": "#f0f2f5"
+ "height": "100%"
},
"subwidgets": [
{
- "widgettype": "Text",
+ "widgettype": "HBox",
"options": {
- "text": "Dashboard",
- "fontSize": "24px",
- "fontWeight": "bold",
- "color": "#333",
- "marginBottom": "20px"
- }
+ "width": "100%",
+ "alignItems": "center",
+ "marginBottom": "24px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Title2",
+ "options": {
+ "text": "数据概览",
+ "color": "#F1F5F9",
+ "fontWeight": "700"
+ }
+ },
+ {
+ "widgettype": "Filler"
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "最后更新: {{get_today_usage(request) and request._run_ns.curDateString() or ''}}",
+ "fontSize": "13px",
+ "color": "#64748B"
+ }
+ }
+ ]
},
{
"widgettype": "ResponsableBox",
"options": {
"gap": "16px",
- "minWidth": "250px"
+ "minWidth": "220px",
+ "marginBottom": "24px"
},
"subwidgets": [
{
"widgettype": "RefreshWidget",
- "id": "refresh_today_usage",
+ "id": "stat_today_usage",
"options": {
- "period_seconds": 10,
- "url": "{{entire_url('today_usage.ui')}}"
+ "period_seconds": 30,
+ "url": "{{entire_url('stat_today_usage.ui')}}"
}
},
{
"widgettype": "RefreshWidget",
- "id": "refresh_today_amount",
+ "id": "stat_today_amount",
"options": {
- "period_seconds": 10,
- "url": "{{entire_url('today_amount.ui')}}"
+ "period_seconds": 30,
+ "url": "{{entire_url('stat_today_amount.ui')}}"
}
},
{
"widgettype": "RefreshWidget",
- "id": "refresh_total_users",
+ "id": "stat_total_users",
"options": {
- "period_seconds": 10,
- "url": "{{entire_url('total_users.ui')}}"
+ "period_seconds": 60,
+ "url": "{{entire_url('stat_total_users.ui')}}"
}
},
{
"widgettype": "RefreshWidget",
- "id": "refresh_concurrent_users",
+ "id": "stat_concurrent",
"options": {
- "period_seconds": 10,
- "url": "{{entire_url('concurrent_users.ui')}}"
+ "period_seconds": 15,
+ "url": "{{entire_url('stat_concurrent.ui')}}"
}
},
{
"widgettype": "RefreshWidget",
- "id": "refresh_accounting_errors",
+ "id": "stat_errors",
"options": {
- "period_seconds": 10,
- "url": "{{entire_url('accounting_errors.ui')}}"
+ "period_seconds": 30,
+ "url": "{{entire_url('stat_errors.ui')}}"
}
}
]
},
+ {
+ "widgettype": "HBox",
+ "options": {
+ "width": "100%",
+ "gap": "20px",
+ "height": "auto"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "60%",
+ "bgcolor": "#1E293B",
+ "borderRadius": "12px",
+ "padding": "20px",
+ "border": "1px solid #334155"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "HBox",
+ "options": {
+ "width": "100%",
+ "alignItems": "center",
+ "marginBottom": "16px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Title4",
+ "options": {
+ "text": "Top 3 模型(今日调用)",
+ "color": "#F1F5F9",
+ "fontWeight": "600"
+ }
+ },
+ {
+ "widgettype": "Filler"
+ },
+ {
+ "widgettype": "Button",
+ "options": {
+ "label": "刷新",
+ "bgcolor": "#334155",
+ "color": "#94A3B8",
+ "border": "none",
+ "borderRadius": "6px",
+ "padding": "4px 12px",
+ "fontSize": "12px"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "method",
+ "target": "-@RefreshWidget",
+ "method": "render_urldata",
+ "params": {}
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "widgettype": "RefreshWidget",
+ "id": "chart_top_models",
+ "options": {
+ "period_seconds": 30,
+ "url": "{{entire_url('chart_top_models.ui')}}"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "width": "40%",
+ "bgcolor": "#1E293B",
+ "borderRadius": "12px",
+ "padding": "20px",
+ "border": "1px solid #334155"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Title4",
+ "options": {
+ "text": "快捷入口",
+ "color": "#F1F5F9",
+ "fontWeight": "600",
+ "marginBottom": "16px"
+ }
+ },
+ {
+ "widgettype": "ResponsableBox",
+ "options": {
+ "gap": "12px",
+ "minWidth": "120px"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "VBox",
+ "options": {
+ "bgcolor": "#334155",
+ "padding": "16px",
+ "borderRadius": "8px",
+ "cursor": "pointer",
+ "textAlign": "center"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.sage_main_content",
+ "options": {
+ "url": "{{entire_url('/llmage/llm')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": ""
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "模型管理",
+ "color": "#E2E8F0",
+ "fontSize": "13px",
+ "marginTop": "8px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "bgcolor": "#334155",
+ "padding": "16px",
+ "borderRadius": "8px",
+ "cursor": "pointer",
+ "textAlign": "center"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.sage_main_content",
+ "options": {
+ "url": "{{entire_url('/rbac/users')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": ""
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "用户管理",
+ "color": "#E2E8F0",
+ "fontSize": "13px",
+ "marginTop": "8px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "bgcolor": "#334155",
+ "padding": "16px",
+ "borderRadius": "8px",
+ "cursor": "pointer",
+ "textAlign": "center"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.sage_main_content",
+ "options": {
+ "url": "{{entire_url('/rag/kdb')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": ""
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "知识库",
+ "color": "#E2E8F0",
+ "fontSize": "13px",
+ "marginTop": "8px"
+ }
+ }
+ ]
+ },
+ {
+ "widgettype": "VBox",
+ "options": {
+ "bgcolor": "#334155",
+ "padding": "16px",
+ "borderRadius": "8px",
+ "cursor": "pointer",
+ "textAlign": "center"
+ },
+ "binds": [
+ {
+ "wid": "self",
+ "event": "click",
+ "actiontype": "urlwidget",
+ "target": "app.sage_main_content",
+ "options": {
+ "url": "{{entire_url('/llmage/failed_accounting.ui')}}"
+ },
+ "mode": "replace"
+ }
+ ],
+ "subwidgets": [
+ {
+ "widgettype": "Svg",
+ "options": {
+ "svg": ""
+ }
+ },
+ {
+ "widgettype": "Text",
+ "options": {
+ "text": "异常记录",
+ "color": "#E2E8F0",
+ "fontSize": "13px",
+ "marginTop": "8px"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
{
"widgettype": "VBox",
"options": {
- "bgcolor": "#FFFFFF",
- "padding": "24px",
- "borderRadius": "8px",
- "marginTop": "20px",
- "minHeight": "350px",
- "boxShadow": "0 2px 8px rgba(0,0,0,0.1)"
+ "width": "100%",
+ "bgcolor": "#1E293B",
+ "borderRadius": "12px",
+ "padding": "20px",
+ "border": "1px solid #334155",
+ "marginTop": "20px"
},
"subwidgets": [
{
- "widgettype": "Text",
+ "widgettype": "Title4",
"options": {
- "text": "Top 3 模型(今日)",
- "fontSize": "18px",
- "fontWeight": "bold",
- "color": "#333",
+ "text": "用户消费排行(Top 5)",
+ "color": "#F1F5F9",
+ "fontWeight": "600",
"marginBottom": "16px"
}
},
{
"widgettype": "RefreshWidget",
- "id": "refresh_top_models_chart",
+ "id": "table_top_users",
"options": {
- "period_seconds": 10,
- "url": "{{entire_url('top_models_chart.ui')}}"
+ "period_seconds": 30,
+ "url": "{{entire_url('table_top_users.ui')}}"
}
}
]
diff --git a/wwwroot/menu.ui b/wwwroot/menu.ui
index f38a7e9..ca0d2cd 100644
--- a/wwwroot/menu.ui
+++ b/wwwroot/menu.ui
@@ -6,7 +6,7 @@
{
"name": "dashboard",
"label": "数据看板",
- "url": "{{entire_url('index.ui')}}"
+ "url": "{{entire_url('shell.ui')}}"
}
]
}
diff --git a/wwwroot/shell.ui b/wwwroot/shell.ui
new file mode 100644
index 0000000..59aa0d6
--- /dev/null
+++ b/wwwroot/shell.ui
@@ -0,0 +1,147 @@
+{
+ "widgettype": "VBox",
+ "options": {
+ "width": "100%",
+ "height": "100%",
+ "bgcolor": "#0B1120"
+ },
+ "subwidgets": [
+ {
+ "widgettype": "Html",
+ "options": {
+ "html": "