diff --git a/dashboard_for_sage/load_dashboard.py b/dashboard_for_sage/load_dashboard.py
index 9400bee..b60e74a 100644
--- a/dashboard_for_sage/load_dashboard.py
+++ b/dashboard_for_sage/load_dashboard.py
@@ -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
diff --git a/wwwroot/index.ui b/wwwroot/index.ui
index b19f2af..24fb68c 100644
--- a/wwwroot/index.ui
+++ b/wwwroot/index.ui
@@ -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": {
diff --git a/wwwroot/stat_active_users.ui b/wwwroot/stat_active_users.ui
new file mode 100644
index 0000000..2bd56b7
--- /dev/null
+++ b/wwwroot/stat_active_users.ui
@@ -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": "",
+ "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"
+ }
+ }
+ ]
+}
diff --git a/wwwroot/stat_new_users_month.ui b/wwwroot/stat_new_users_month.ui
new file mode 100644
index 0000000..d78847e
--- /dev/null
+++ b/wwwroot/stat_new_users_month.ui
@@ -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": "",
+ "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"
+ }
+ }
+ ]
+}
diff --git a/wwwroot/stat_total_orgs.ui b/wwwroot/stat_total_orgs.ui
new file mode 100644
index 0000000..b71931c
--- /dev/null
+++ b/wwwroot/stat_total_orgs.ui
@@ -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": "",
+ "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"
+ }
+ }
+ ]
+}