fix dashboard: clickable quick entries, full-data ranking tables, dark bg

1. Quick entry shortcuts: Replace VBox (no native click) with Button
   widgets for reliable click event handling. Each entry wraps its
   icon+text in a VBox inside the Button for layout.

2. Ranking tables: Changed get_top_users/ providers queries from
   today-only (WHERE use_date=today) to all-time data so transaction
   counts and amounts always display meaningful results.

3. Background color: Added bgcolor=#0B1120 to top-level index.ui VBox
   so the page background matches the shell theme, eliminating the
   white-vs-dark-blue contrast.
This commit is contained in:
yumoqing 2026-05-26 16:06:54 +08:00
parent 95d18e7ce0
commit 8ceb769356
3 changed files with 99 additions and 74 deletions

View File

@ -95,9 +95,8 @@ async def get_accounting_errors(request):
async def get_top_users_by_amount(request): async def get_top_users_by_amount(request):
"""获取当天用户金额前5""" """获取用户金额前5(全量)"""
env = request._run_ns env = request._run_ns
today = env.curDateString()
async with get_sor_context(env, 'sage') as sor: async with get_sor_context(env, 'sage') as sor:
sql = """ sql = """
SELECT SELECT
@ -106,12 +105,11 @@ async def get_top_users_by_amount(request):
COUNT(*) as cnt COUNT(*) as cnt
FROM llmusage a FROM llmusage a
LEFT JOIN users b ON a.userid = b.id LEFT JOIN users b ON a.userid = b.id
WHERE a.use_date = ${today}$
GROUP BY a.userid, b.nick_name, b.username GROUP BY a.userid, b.nick_name, b.username
ORDER BY total_amount DESC ORDER BY total_amount DESC
LIMIT 5 LIMIT 5
""" """
recs = await sor.sqlExe(sql, {'today': today}) recs = await sor.sqlExe(sql, {})
result = [] result = []
for r in recs: for r in recs:
result.append({ result.append({
@ -123,9 +121,8 @@ async def get_top_users_by_amount(request):
async def get_top_users_by_count(request): async def get_top_users_by_count(request):
"""获取当天用户笔数前5""" """获取用户笔数前5(全量)"""
env = request._run_ns env = request._run_ns
today = env.curDateString()
async with get_sor_context(env, 'sage') as sor: async with get_sor_context(env, 'sage') as sor:
sql = """ sql = """
SELECT SELECT
@ -134,12 +131,11 @@ async def get_top_users_by_count(request):
COALESCE(SUM(a.amount), 0) as total_amount COALESCE(SUM(a.amount), 0) as total_amount
FROM llmusage a FROM llmusage a
LEFT JOIN users b ON a.userid = b.id LEFT JOIN users b ON a.userid = b.id
WHERE a.use_date = ${today}$
GROUP BY a.userid, b.nick_name, b.username GROUP BY a.userid, b.nick_name, b.username
ORDER BY cnt DESC ORDER BY cnt DESC
LIMIT 5 LIMIT 5
""" """
recs = await sor.sqlExe(sql, {'today': today}) recs = await sor.sqlExe(sql, {})
result = [] result = []
for r in recs: for r in recs:
result.append({ result.append({
@ -151,9 +147,8 @@ async def get_top_users_by_count(request):
async def get_top_providers_by_amount(request): async def get_top_providers_by_amount(request):
"""获取模型供应商金额前5""" """获取模型供应商金额前5(全量)"""
env = request._run_ns env = request._run_ns
today = env.curDateString()
async with get_sor_context(env, 'sage') as sor: async with get_sor_context(env, 'sage') as sor:
sql = """ sql = """
SELECT SELECT
@ -163,12 +158,11 @@ async def get_top_providers_by_amount(request):
FROM llmusage a FROM llmusage a
LEFT JOIN llm b ON a.llmid = b.id LEFT JOIN llm b ON a.llmid = b.id
LEFT JOIN organization c ON b.providerid = c.id LEFT JOIN organization c ON b.providerid = c.id
WHERE a.use_date = ${today}$
GROUP BY b.providerid, c.orgname GROUP BY b.providerid, c.orgname
ORDER BY total_amount DESC ORDER BY total_amount DESC
LIMIT 5 LIMIT 5
""" """
recs = await sor.sqlExe(sql, {'today': today}) recs = await sor.sqlExe(sql, {})
result = [] result = []
for r in recs: for r in recs:
result.append({ result.append({
@ -180,9 +174,8 @@ async def get_top_providers_by_amount(request):
async def get_top_providers_by_count(request): async def get_top_providers_by_count(request):
"""获取模型供应商笔数前5""" """获取模型供应商笔数前5(全量)"""
env = request._run_ns env = request._run_ns
today = env.curDateString()
async with get_sor_context(env, 'sage') as sor: async with get_sor_context(env, 'sage') as sor:
sql = """ sql = """
SELECT SELECT
@ -192,12 +185,11 @@ async def get_top_providers_by_count(request):
FROM llmusage a FROM llmusage a
LEFT JOIN llm b ON a.llmid = b.id LEFT JOIN llm b ON a.llmid = b.id
LEFT JOIN organization c ON b.providerid = c.id LEFT JOIN organization c ON b.providerid = c.id
WHERE a.use_date = ${today}$
GROUP BY b.providerid, c.orgname GROUP BY b.providerid, c.orgname
ORDER BY cnt DESC ORDER BY cnt DESC
LIMIT 5 LIMIT 5
""" """
recs = await sor.sqlExe(sql, {'today': today}) recs = await sor.sqlExe(sql, {})
result = [] result = []
for r in recs: for r in recs:
result.append({ result.append({

View File

@ -11,7 +11,7 @@
"name": "dashboard", "name": "dashboard",
"label": "仪表盘", "label": "仪表盘",
"icon": "fa fa-dashboard", "icon": "fa fa-dashboard",
"url": "{{entire_url('index.ui')}}", "url": "{{entire_url('/dashboard_for_sage/index.ui')}}",
"target": "app.sage_main_content" "target": "app.sage_main_content"
}, },
{% if get_user() %} {% if get_user() %}

View File

@ -2,7 +2,8 @@
"widgettype": "VBox", "widgettype": "VBox",
"options": { "options": {
"width": "100%", "width": "100%",
"height": "100%" "height": "100%",
"bgcolor": "#0B1120"
}, },
"subwidgets": [ "subwidgets": [
{ {
@ -216,12 +217,12 @@
}, },
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "VBox", "widgettype": "Button",
"options": { "options": {
"bgcolor": "#334155", "bgcolor": "#334155",
"padding": "16px", "padding": "16px",
"borderRadius": "8px", "borderRadius": "8px",
"cursor": "pointer", "border": "none",
"textAlign": "center" "textAlign": "center"
}, },
"binds": [ "binds": [
@ -238,29 +239,37 @@
], ],
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "Svg", "widgettype": "VBox",
"options": { "options": {
"svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#90caf9\" stroke-width=\"2\"><path d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15.75c-2.062 0-4.024-.614-5.67-1.757l-1.57-.393m15.04 0L12 21 5.25 13.893\"/></svg>" "alignItems": "center"
} },
}, "subwidgets": [
{ {
"widgettype": "Text", "widgettype": "Svg",
"options": { "options": {
"text": "模型管理", "svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#90caf9\" stroke-width=\"2\"><path d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15.75c-2.062 0-4.024-.614-5.67-1.757l-1.57-.393m15.04 0L12 21 5.25 13.893\"/></svg>"
"color": "#E2E8F0", }
"fontSize": "13px", },
"marginTop": "8px" {
} "widgettype": "Text",
"options": {
"text": "模型管理",
"color": "#E2E8F0",
"fontSize": "13px",
"marginTop": "8px"
}
}
]
} }
] ]
}, },
{ {
"widgettype": "VBox", "widgettype": "Button",
"options": { "options": {
"bgcolor": "#334155", "bgcolor": "#334155",
"padding": "16px", "padding": "16px",
"borderRadius": "8px", "borderRadius": "8px",
"cursor": "pointer", "border": "none",
"textAlign": "center" "textAlign": "center"
}, },
"binds": [ "binds": [
@ -277,29 +286,37 @@
], ],
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "Svg", "widgettype": "VBox",
"options": { "options": {
"svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#4caf50\" stroke-width=\"2\"><path d=\"M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z\"/></svg>" "alignItems": "center"
} },
}, "subwidgets": [
{ {
"widgettype": "Text", "widgettype": "Svg",
"options": { "options": {
"text": "用户管理", "svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#4caf50\" stroke-width=\"2\"><path d=\"M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z\"/></svg>"
"color": "#E2E8F0", }
"fontSize": "13px", },
"marginTop": "8px" {
} "widgettype": "Text",
"options": {
"text": "用户管理",
"color": "#E2E8F0",
"fontSize": "13px",
"marginTop": "8px"
}
}
]
} }
] ]
}, },
{ {
"widgettype": "VBox", "widgettype": "Button",
"options": { "options": {
"bgcolor": "#334155", "bgcolor": "#334155",
"padding": "16px", "padding": "16px",
"borderRadius": "8px", "borderRadius": "8px",
"cursor": "pointer", "border": "none",
"textAlign": "center" "textAlign": "center"
}, },
"binds": [ "binds": [
@ -316,29 +333,37 @@
], ],
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "Svg", "widgettype": "VBox",
"options": { "options": {
"svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#f59e0b\" stroke-width=\"2\"><path d=\"M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125\"/></svg>" "alignItems": "center"
} },
}, "subwidgets": [
{ {
"widgettype": "Text", "widgettype": "Svg",
"options": { "options": {
"text": "知识库", "svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#f59e0b\" stroke-width=\"2\"><path d=\"M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125\"/></svg>"
"color": "#E2E8F0", }
"fontSize": "13px", },
"marginTop": "8px" {
} "widgettype": "Text",
"options": {
"text": "知识库",
"color": "#E2E8F0",
"fontSize": "13px",
"marginTop": "8px"
}
}
]
} }
] ]
}, },
{ {
"widgettype": "VBox", "widgettype": "Button",
"options": { "options": {
"bgcolor": "#334155", "bgcolor": "#334155",
"padding": "16px", "padding": "16px",
"borderRadius": "8px", "borderRadius": "8px",
"cursor": "pointer", "border": "none",
"textAlign": "center" "textAlign": "center"
}, },
"binds": [ "binds": [
@ -355,19 +380,27 @@
], ],
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "Svg", "widgettype": "VBox",
"options": { "options": {
"svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2\"><path d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"/></svg>" "alignItems": "center"
} },
}, "subwidgets": [
{ {
"widgettype": "Text", "widgettype": "Svg",
"options": { "options": {
"text": "异常记录", "svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2\"><path d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"/></svg>"
"color": "#E2E8F0", }
"fontSize": "13px", },
"marginTop": "8px" {
} "widgettype": "Text",
"options": {
"text": "异常记录",
"color": "#E2E8F0",
"fontSize": "13px",
"marginTop": "8px"
}
}
]
} }
] ]
} }