yumoqing 98a28d9770 feat: 添加全局今日模型使用图表,替换用户级监控为全量监控
- 新增 get_all_today_models() 函数查询所有用户今日模型使用
- 新增 all_today_models_chart.ui 和 api/all_today_models.dspy
- index.ui 标题改为「今日模型使用(全部)」,数据源改为全量
- load_path.py 注册新路径权限
- 保留原有 user_today_models 供后续个性化需求使用
2026-05-31 10:01:28 +08:00

155 lines
5.3 KiB
Python

"""Generate RBAC permissions for dashboard_for_sage module paths.
Run from Sage root with Sage venv:
cd ~/repos/sage && ./py3/bin/python ../dashboard_for_sage/scripts/load_path.py
Or set SAGE_ROOT environment variable.
"""
import os
import sys
import asyncio
# Ensure Sage root is in path
sage_root = os.environ.get('SAGE_ROOT')
if sage_root and sage_root not in sys.path:
sys.path.insert(0, sage_root)
from sqlor.dbpools import DBPools
from appPublic.jsonConfig import getConfig
from appPublic.dictObject import DictObject
from appPublic.uniqueID import getID
# ── Permission definitions ──
# Format: (path, role)
# Dashboard files are accessible to all logined users except menu.ui (any)
paths = [
# Module root and index
("/dashboard_for_sage", "logined"),
("/dashboard_for_sage/index.ui", "logined"),
# Menu — must be any so unauthenticated users can see nav
("/dashboard_for_sage/menu.ui", "any"),
# Shell
("/dashboard_for_sage/shell.ui", "logined"),
("/dashboard_for_sage/shell_theme.css", "any"),
("/dashboard_for_sage/shell_theme.js", "any"),
# Global menu
("/dashboard_for_sage/global_menu.ui", "logined"),
# Stat cards
("/dashboard_for_sage/stat_today_usage.ui", "logined"),
("/dashboard_for_sage/stat_today_amount.ui", "logined"),
("/dashboard_for_sage/stat_total_users.ui", "logined"),
("/dashboard_for_sage/stat_active_users.ui", "logined"),
("/dashboard_for_sage/stat_concurrent.ui", "logined"),
("/dashboard_for_sage/stat_errors.ui", "logined"),
("/dashboard_for_sage/stat_new_users_month.ui", "logined"),
("/dashboard_for_sage/stat_total_orgs.ui", "logined"),
# Legacy stat cards (backward compat)
("/dashboard_for_sage/today_usage.ui", "logined"),
("/dashboard_for_sage/today_amount.ui", "logined"),
("/dashboard_for_sage/total_users.ui", "logined"),
("/dashboard_for_sage/concurrent_users.ui", "logined"),
("/dashboard_for_sage/accounting_errors.ui", "logined"),
# Top 5 ranking cards
("/dashboard_for_sage/table_top_users.ui", "logined"),
("/dashboard_for_sage/table_top_users_amount.ui", "logined"),
("/dashboard_for_sage/table_top_users_count.ui", "logined"),
("/dashboard_for_sage/table_top_providers_amount.ui", "logined"),
("/dashboard_for_sage/table_top_providers_count.ui", "logined"),
("/dashboard_for_sage/top_users_amount.ui", "logined"),
# Charts
("/dashboard_for_sage/chart_top_models.ui", "logined"),
("/dashboard_for_sage/top_models_chart.ui", "logined"),
("/dashboard_for_sage/user_today_models_chart.ui", "logined"),
("/dashboard_for_sage/all_today_models_chart.ui", "logined"),
# Customer monitoring
("/dashboard_for_sage/customer_usage.ui", "logined"),
("/dashboard_for_sage/customer_daily_chart.ui", "logined"),
("/dashboard_for_sage/customer_monthly_chart.ui", "logined"),
("/dashboard_for_sage/customer_daily_trend.ui", "logined"),
# API endpoints
("/dashboard_for_sage/api/top_models.dspy", "logined"),
("/dashboard_for_sage/api/user_today_models.dspy", "logined"),
("/dashboard_for_sage/api/all_today_models.dspy", "logined"),
("/dashboard_for_sage/api/customer_daily_models.dspy", "logined"),
("/dashboard_for_sage/api/customer_monthly_models.dspy", "logined"),
("/dashboard_for_sage/api/customer_daily_trend.dspy", "logined"),
]
async def add_roleperm(sor, roleid, permid):
"""Add role-permission mapping if not exists."""
ns = {'roleid': roleid, 'permid': permid}
recs = await sor.R('rolepermission', ns.copy())
if not recs:
ns['id'] = getID()
await sor.C('rolepermission', ns.copy())
async def add_roles_perm(sor, perm, roles):
"""Register permission for special roles."""
if roles in [['any'], ['anonymous'], ['logined']]:
role = roles[0]
await add_roleperm(sor, role, perm.id)
return
for role in roles:
if '.' in role:
orgtypeid, name = role.split('.', 1)
else:
orgtypeid, name = '*', role
ns = {'orgtypeid': orgtypeid, 'name': name}
roles_rec = await sor.R('role', ns.copy())
if not roles_rec:
ns['id'] = getID()
await sor.C('role', ns.copy())
else:
ns['id'] = roles_rec[0].id
await add_roleperm(sor, ns['id'], perm.id)
# Remove 'any' fallback for this perm
ns_any = {'roleid': 'any', 'permid': perm.id}
existing = await sor.R('rolepermission', ns_any.copy())
if existing:
await sor.D('rolepermission', {'id': existing[0].id})
async def main():
config = getConfig('.')
db = DBPools(config.databases)
cnt = 0
async with db.sqlorContext('sage') as sor:
for path, role in paths:
ns = {'path': path}
recs = await sor.R('permission', ns.copy())
if recs:
# Permission exists, skip (idempotent)
continue
cnt += 1
pid = getID()
ns['id'] = pid
await sor.C('permission', ns.copy())
perm = DictObject(**ns)
await add_roles_perm(sor, perm, [role])
print(f'{cnt} path(s) inserted for dashboard_for_sage')
if cnt == 0:
print('All paths already registered — no changes needed.')
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())