feat: add user statistics cards to dashboard

- Add get_active_users_today(), get_new_users_month(), get_total_orgs() to load_dashboard.py
- Create stat_active_users.ui, stat_new_users_month.ui, stat_total_orgs.ui widgets
- Add active users card to main stats row
- Add new row with new users this month and total organizations cards
This commit is contained in:
yumoqing 2026-05-25 18:49:25 +08:00
parent ffdc7fc983
commit be1ac95ac7
5 changed files with 231 additions and 0 deletions

View File

@ -208,6 +208,44 @@ async def get_top_providers_by_count(request):
return result
async def get_active_users_today(request):
"""获取今日活跃用户数今日有llmusage记录的去重用户"""
env = request._run_ns
today = env.curDateString()
tomorrow = (datetime.now() + timedelta(days=1)).strftime('%Y-%m-%d')
async with get_sor_context(env, 'sage') as sor:
sql = """
SELECT COUNT(DISTINCT userid) as cnt FROM llmusage
WHERE use_date >= ${today}$ AND use_date < ${tomorrow}$
"""
recs = await sor.sqlExe(sql, {'today': today, 'tomorrow': tomorrow})
cnt = int(recs[0].get('cnt', 0)) if recs else 0
return cnt
async def get_new_users_month(request):
"""获取本月新增用户数"""
env = request._run_ns
month_start = datetime.now().strftime('%Y-%m-01')
async with get_sor_context(env, 'sage') as sor:
sql = """
SELECT COUNT(*) as cnt FROM users WHERE created_date >= ${month_start}$
"""
recs = await sor.sqlExe(sql, {'month_start': month_start})
cnt = int(recs[0].get('cnt', 0)) if recs else 0
return cnt
async def get_total_orgs(request):
"""获取组织机构总数"""
env = request._run_ns
async with get_sor_context(env, 'sage') as sor:
sql = "SELECT COUNT(*) as cnt FROM organization"
recs = await sor.sqlExe(sql, {})
cnt = int(recs[0].get('cnt', 0)) if recs else 0
return cnt
def load_dashboard():
"""Register dashboard functions on ServerEnv"""
g = ServerEnv()
@ -221,3 +259,6 @@ def load_dashboard():
g.get_top_users_by_count = get_top_users_by_count
g.get_top_providers_by_amount = get_top_providers_by_amount
g.get_top_providers_by_count = get_top_providers_by_count
g.get_active_users_today = get_active_users_today
g.get_new_users_month = get_new_users_month
g.get_total_orgs = get_total_orgs

View File

@ -66,6 +66,14 @@
"url": "{{entire_url('stat_total_users.ui')}}"
}
},
{
"widgettype": "RefreshWidget",
"id": "stat_active_users",
"options": {
"period_seconds": 60,
"url": "{{entire_url('stat_active_users.ui')}}"
}
},
{
"widgettype": "RefreshWidget",
"id": "stat_concurrent",
@ -84,6 +92,32 @@
}
]
},
{
"widgettype": "ResponsableBox",
"options": {
"gap": "16px",
"minWidth": "220px",
"marginBottom": "24px"
},
"subwidgets": [
{
"widgettype": "RefreshWidget",
"id": "stat_new_users_month",
"options": {
"period_seconds": 120,
"url": "{{entire_url('stat_new_users_month.ui')}}"
}
},
{
"widgettype": "RefreshWidget",
"id": "stat_total_orgs",
"options": {
"period_seconds": 120,
"url": "{{entire_url('stat_total_orgs.ui')}}"
}
}
]
},
{
"widgettype": "HBox",
"options": {

View File

@ -0,0 +1,52 @@
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "20px",
"borderRadius": "12px",
"border": "1px solid #334155",
"flex": "1",
"minHeight": "110px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "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=\"M15.91 11.672a.375.375 0 010 .656l-5.603 3.113a.375.375 0 01-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112z\"/><path d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg>",
"width": "24px",
"height": "24px"
}
},
{
"widgettype": "Filler"
}
]
},
{
"widgettype": "Text",
"options": {
"text": "{{get_active_users_today(request)}}",
"fontSize": "32px",
"fontWeight": "700",
"color": "#F1F5F9",
"lineHeight": "1.1"
}
},
{
"widgettype": "Text",
"options": {
"text": "今日活跃用户",
"fontSize": "14px",
"color": "#94A3B8",
"marginTop": "4px"
}
}
]
}

View File

@ -0,0 +1,52 @@
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "20px",
"borderRadius": "12px",
"border": "1px solid #334155",
"flex": "1",
"minHeight": "110px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "center",
"marginBottom": "12px"
},
"subwidgets": [
{
"widgettype": "Svg",
"options": {
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#10B981\" stroke-width=\"2\"><path d=\"M19 7.5v3m0 0v3m0 0v3m0 0v3m0 0h-3m0 0h-3m0 0h-3m0 0h-3m0 0v-3m0 0V12m0 0V7.5M5 21h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2z\"/></svg>",
"width": "24px",
"height": "24px"
}
},
{
"widgettype": "Filler"
}
]
},
{
"widgettype": "Text",
"options": {
"text": "{{get_new_users_month(request)}}",
"fontSize": "32px",
"fontWeight": "700",
"color": "#F1F5F9",
"lineHeight": "1.1"
}
},
{
"widgettype": "Text",
"options": {
"text": "本月新增用户",
"fontSize": "14px",
"color": "#94A3B8",
"marginTop": "4px"
}
}
]
}

View File

@ -0,0 +1,52 @@
{
"widgettype": "VBox",
"options": {
"bgcolor": "#1E293B",
"padding": "20px",
"borderRadius": "12px",
"border": "1px solid #334155",
"flex": "1",
"minHeight": "110px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "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=\"M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h1.5m-1.5 3h1.5m-1.5 3h1.5m3-6H15m-1.5 3H15m-1.5 3H15M9 21v-3.375c0-.621.504-1.125 1.125-1.125h3.75c.621 0 1.125.504 1.125 1.125V21\"/></svg>",
"width": "24px",
"height": "24px"
}
},
{
"widgettype": "Filler"
}
]
},
{
"widgettype": "Text",
"options": {
"text": "{{get_total_orgs(request)}}",
"fontSize": "32px",
"fontWeight": "700",
"color": "#F1F5F9",
"lineHeight": "1.1"
}
},
{
"widgettype": "Text",
"options": {
"text": "组织机构数",
"fontSize": "14px",
"color": "#94A3B8",
"marginTop": "4px"
}
}
]
}