Compare commits
No commits in common. "main" and "feat/modern-ui-redesign" have entirely different histories.
main
...
feat/moder
@ -1,96 +1,31 @@
|
||||
"""Dashboard data functions - exposed via load_dashboard() on ServerEnv"""
|
||||
# Force re-sync: file was truncated in working copy on some deployments
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from sqlor.dbpools import get_sor_context
|
||||
from datetime import datetime, timedelta, date
|
||||
|
||||
|
||||
async def get_today_usage(request):
|
||||
"""获取当天llmusage笔数(排除记账失败)"""
|
||||
"""获取当天llmusage笔数"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = "SELECT COUNT(*) as cnt FROM llmusage WHERE use_date = ${today}$ AND accounting_status = 'accounted'"
|
||||
sql = "SELECT COUNT(*) as cnt FROM llmusage WHERE use_date = ${today}$"
|
||||
recs = await sor.sqlExe(sql, {'today': today})
|
||||
cnt = int(recs[0].get('cnt', 0)) if recs else 0
|
||||
return cnt
|
||||
|
||||
|
||||
async def get_today_amount(request):
|
||||
"""获取当天交易金额(排除记账失败)"""
|
||||
"""获取当天交易金额"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = "SELECT COALESCE(SUM(amount), 0) as total_amount FROM llmusage WHERE use_date = ${today}$ AND accounting_status = 'accounted'"
|
||||
sql = "SELECT COALESCE(SUM(amount), 0) as total_amount FROM llmusage WHERE use_date = ${today}$"
|
||||
recs = await sor.sqlExe(sql, {'today': today})
|
||||
amount = float(recs[0].get('total_amount', 0)) if recs else 0.0
|
||||
return amount
|
||||
|
||||
|
||||
async def get_usage_trend(request):
|
||||
"""计算今日调用量趋势(与昨日对比)"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
yesterday = (date.today() - timedelta(days=1)).isoformat()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
# 今日
|
||||
sql_today = "SELECT COUNT(*) as cnt FROM llmusage WHERE use_date = ${today}$ AND accounting_status = 'accounted'"
|
||||
recs_today = await sor.sqlExe(sql_today, {'today': today})
|
||||
today_cnt = int(recs_today[0].get('cnt', 0)) if recs_today else 0
|
||||
|
||||
# 昨日
|
||||
sql_yesterday = "SELECT COUNT(*) as cnt FROM llmusage WHERE use_date = ${yesterday}$ AND accounting_status = 'accounted'"
|
||||
recs_yesterday = await sor.sqlExe(sql_yesterday, {'yesterday': yesterday})
|
||||
yesterday_cnt = int(recs_yesterday[0].get('cnt', 0)) if recs_yesterday else 0
|
||||
|
||||
# 计算趋势
|
||||
if yesterday_cnt == 0:
|
||||
return {'trend': 'flat', 'percentage': 0, 'value': today_cnt}
|
||||
|
||||
change_pct = ((today_cnt - yesterday_cnt) / yesterday_cnt) * 100
|
||||
|
||||
if change_pct > 5:
|
||||
trend = 'up'
|
||||
elif change_pct < -5:
|
||||
trend = 'down'
|
||||
else:
|
||||
trend = 'flat'
|
||||
|
||||
return {'trend': trend, 'percentage': abs(change_pct), 'value': today_cnt}
|
||||
|
||||
|
||||
async def get_amount_trend(request):
|
||||
"""计算今日金额趋势(与昨日对比)"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
yesterday = (date.today() - timedelta(days=1)).isoformat()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
# 今日
|
||||
sql_today = "SELECT COALESCE(SUM(amount), 0) as total_amount FROM llmusage WHERE use_date = ${today}$ AND accounting_status = 'accounted'"
|
||||
recs_today = await sor.sqlExe(sql_today, {'today': today})
|
||||
today_amount = float(recs_today[0].get('total_amount', 0)) if recs_today else 0.0
|
||||
|
||||
# 昨日
|
||||
sql_yesterday = "SELECT COALESCE(SUM(amount), 0) as total_amount FROM llmusage WHERE use_date = ${yesterday}$ AND accounting_status = 'accounted'"
|
||||
recs_yesterday = await sor.sqlExe(sql_yesterday, {'yesterday': yesterday})
|
||||
yesterday_amount = float(recs_yesterday[0].get('total_amount', 0)) if recs_yesterday else 0.0
|
||||
|
||||
# 计算趋势
|
||||
if yesterday_amount == 0:
|
||||
return {'trend': 'flat', 'percentage': 0, 'value': today_amount}
|
||||
|
||||
change_pct = ((today_amount - yesterday_amount) / yesterday_amount) * 100
|
||||
|
||||
if change_pct > 5:
|
||||
trend = 'up'
|
||||
elif change_pct < -5:
|
||||
trend = 'down'
|
||||
else:
|
||||
trend = 'flat'
|
||||
|
||||
return {'trend': trend, 'percentage': abs(change_pct), 'value': today_amount}
|
||||
|
||||
|
||||
async def get_total_users(request):
|
||||
"""获取用户总数"""
|
||||
env = request._run_ns
|
||||
@ -135,7 +70,7 @@ async def get_top_models(request):
|
||||
WHERE a.use_date = ${today}$
|
||||
GROUP BY a.llmid, b.name
|
||||
ORDER BY cnt DESC
|
||||
LIMIT 5
|
||||
LIMIT 3
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {'today': today})
|
||||
result = []
|
||||
@ -160,8 +95,9 @@ async def get_accounting_errors(request):
|
||||
|
||||
|
||||
async def get_top_users_by_amount(request):
|
||||
"""获取用户金额前5(全量)"""
|
||||
"""获取当天用户金额前5"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
@ -170,11 +106,12 @@ async def get_top_users_by_amount(request):
|
||||
COUNT(*) as cnt
|
||||
FROM llmusage a
|
||||
LEFT JOIN users b ON a.userid = b.id
|
||||
WHERE a.use_date = ${today}$
|
||||
GROUP BY a.userid, b.nick_name, b.username
|
||||
ORDER BY total_amount DESC
|
||||
LIMIT 5
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {})
|
||||
recs = await sor.sqlExe(sql, {'today': today})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
@ -186,8 +123,9 @@ async def get_top_users_by_amount(request):
|
||||
|
||||
|
||||
async def get_top_users_by_count(request):
|
||||
"""获取用户笔数前5(全量)"""
|
||||
"""获取当天用户笔数前5"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
@ -196,11 +134,12 @@ async def get_top_users_by_count(request):
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
LEFT JOIN users b ON a.userid = b.id
|
||||
WHERE a.use_date = ${today}$
|
||||
GROUP BY a.userid, b.nick_name, b.username
|
||||
ORDER BY cnt DESC
|
||||
LIMIT 5
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {})
|
||||
recs = await sor.sqlExe(sql, {'today': today})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
@ -212,8 +151,9 @@ async def get_top_users_by_count(request):
|
||||
|
||||
|
||||
async def get_top_providers_by_amount(request):
|
||||
"""获取模型供应商金额前5(全量)"""
|
||||
"""获取模型供应商金额前5"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
@ -223,11 +163,12 @@ async def get_top_providers_by_amount(request):
|
||||
FROM llmusage a
|
||||
LEFT JOIN llm b ON a.llmid = b.id
|
||||
LEFT JOIN organization c ON b.providerid = c.id
|
||||
WHERE a.use_date = ${today}$
|
||||
GROUP BY b.providerid, c.orgname
|
||||
ORDER BY total_amount DESC
|
||||
LIMIT 5
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {})
|
||||
recs = await sor.sqlExe(sql, {'today': today})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
@ -239,8 +180,9 @@ async def get_top_providers_by_amount(request):
|
||||
|
||||
|
||||
async def get_top_providers_by_count(request):
|
||||
"""获取模型供应商笔数前5(全量)"""
|
||||
"""获取模型供应商笔数前5"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
@ -250,338 +192,18 @@ async def get_top_providers_by_count(request):
|
||||
FROM llmusage a
|
||||
LEFT JOIN llm b ON a.llmid = b.id
|
||||
LEFT JOIN organization c ON b.providerid = c.id
|
||||
WHERE a.use_date = ${today}$
|
||||
GROUP BY b.providerid, c.orgname
|
||||
ORDER BY cnt DESC
|
||||
LIMIT 5
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'provider_name': r.get('provider_name', 'Unknown'),
|
||||
'cnt': int(r.get('cnt', 0)),
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 2)
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
async def get_top_users_combined(request):
|
||||
"""Top 5 users by amount with count - for combined ChartBar"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
COALESCE(b.nick_name, b.username) as user_name,
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
LEFT JOIN users b ON a.userid = b.id
|
||||
GROUP BY a.userid, b.nick_name, b.username
|
||||
ORDER BY total_amount DESC
|
||||
LIMIT 5
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'user_name': r.get('user_name', 'Unknown'),
|
||||
'cnt': int(r.get('cnt', 0)),
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 2)
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
async def get_top_providers_combined(request):
|
||||
"""Top 5 providers by amount with count - for combined ChartBar"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
COALESCE(c.orgname, 'Unknown') as provider_name,
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
LEFT JOIN llm b ON a.llmid = b.id
|
||||
LEFT JOIN organization c ON b.providerid = c.id
|
||||
GROUP BY b.providerid, c.orgname
|
||||
ORDER BY total_amount DESC
|
||||
LIMIT 5
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'provider_name': r.get('provider_name', 'Unknown'),
|
||||
'cnt': int(r.get('cnt', 0)),
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 2)
|
||||
})
|
||||
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_at >= ${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
|
||||
|
||||
|
||||
async def get_user_today_models(request):
|
||||
"""获取当前用户当天各模型调用次数和金额(用户级监控项)"""
|
||||
env = request._run_ns
|
||||
userid = await env.get_user()
|
||||
if not userid:
|
||||
return []
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
COALESCE(b.name, 'Unknown') as model_name,
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
LEFT JOIN llm b ON a.llmid = b.id
|
||||
WHERE a.use_date = ${today}$
|
||||
AND a.userid = ${userid}$
|
||||
GROUP BY a.llmid, b.name
|
||||
ORDER BY cnt DESC
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {'today': today, 'userid': userid})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'model_name': r.get('model_name', 'Unknown'),
|
||||
'cnt': int(r.get('cnt', 0)),
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 4)
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
async def get_all_today_models(request):
|
||||
"""获取所有用户当天各模型调用次数和金额(全局监控项)"""
|
||||
env = request._run_ns
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
COALESCE(b.name, 'Unknown') as model_name,
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
LEFT JOIN llm b ON a.llmid = b.id
|
||||
WHERE a.use_date = ${today}$
|
||||
GROUP BY a.llmid, b.name
|
||||
ORDER BY cnt DESC
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {'today': today})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'model_name': r.get('model_name', 'Unknown'),
|
||||
'provider_name': r.get('provider_name', 'Unknown'),
|
||||
'cnt': int(r.get('cnt', 0)),
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 4)
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
# ── Customer (org-level) monitoring functions ──
|
||||
|
||||
|
||||
async def _get_org_id(request):
|
||||
"""Helper: get current user's org_id from request context."""
|
||||
env = request._run_ns
|
||||
org_id = await env.get_userorgid()
|
||||
return org_id or '0'
|
||||
|
||||
|
||||
async def get_customer_daily_models(request):
|
||||
"""获取当前客户组织当天各模型调用次数和金额"""
|
||||
env = request._run_ns
|
||||
org_id = await _get_org_id(request)
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
COALESCE(b.name, 'Unknown') as model_name,
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
LEFT JOIN llm b ON a.llmid = b.id
|
||||
WHERE a.use_date = ${today}$
|
||||
AND a.userorgid = ${org_id}$
|
||||
GROUP BY a.llmid, b.name
|
||||
ORDER BY cnt DESC
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {'today': today, 'org_id': org_id})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'model_name': r.get('model_name', 'Unknown'),
|
||||
'cnt': int(r.get('cnt', 0)),
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 4)
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
async def get_customer_monthly_models(request):
|
||||
"""获取当前客户组织当月各模型调用次数和金额"""
|
||||
env = request._run_ns
|
||||
org_id = await _get_org_id(request)
|
||||
now = datetime.now()
|
||||
month_start = now.strftime('%Y-%m-01')
|
||||
if now.month == 12:
|
||||
month_end = f'{now.year + 1}-01-01'
|
||||
else:
|
||||
month_end = f'{now.year}-{now.month + 1:02d}-01'
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
COALESCE(b.name, 'Unknown') as model_name,
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
LEFT JOIN llm b ON a.llmid = b.id
|
||||
WHERE a.use_date >= ${month_start}$
|
||||
AND a.use_date < ${month_end}$
|
||||
AND a.userorgid = ${org_id}$
|
||||
GROUP BY a.llmid, b.name
|
||||
ORDER BY cnt DESC
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {
|
||||
'month_start': month_start,
|
||||
'month_end': month_end,
|
||||
'org_id': org_id
|
||||
})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'model_name': r.get('model_name', 'Unknown'),
|
||||
'cnt': int(r.get('cnt', 0)),
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 4)
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
async def get_customer_daily_summary(request):
|
||||
"""获取当前客户组织今日汇总(调用次数+金额)"""
|
||||
env = request._run_ns
|
||||
org_id = await _get_org_id(request)
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
WHERE a.use_date = ${today}$
|
||||
AND a.userorgid = ${org_id}$
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {'today': today, 'org_id': org_id})
|
||||
if recs:
|
||||
return {
|
||||
'cnt': int(recs[0].get('cnt', 0)),
|
||||
'total_amount': round(float(recs[0].get('total_amount', 0)), 4)
|
||||
}
|
||||
return {'cnt': 0, 'total_amount': 0}
|
||||
|
||||
|
||||
async def get_customer_month_summary(request):
|
||||
"""获取当前客户组织当月汇总(调用次数+金额)"""
|
||||
env = request._run_ns
|
||||
org_id = await _get_org_id(request)
|
||||
now = datetime.now()
|
||||
month_start = now.strftime('%Y-%m-01')
|
||||
if now.month == 12:
|
||||
month_end = f'{now.year + 1}-01-01'
|
||||
else:
|
||||
month_end = f'{now.year}-{now.month + 1:02d}-01'
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
WHERE a.use_date >= ${month_start}$
|
||||
AND a.use_date < ${month_end}$
|
||||
AND a.userorgid = ${org_id}$
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {
|
||||
'month_start': month_start,
|
||||
'month_end': month_end,
|
||||
'org_id': org_id
|
||||
})
|
||||
if recs:
|
||||
return {
|
||||
'cnt': int(recs[0].get('cnt', 0)),
|
||||
'total_amount': round(float(recs[0].get('total_amount', 0)), 4)
|
||||
}
|
||||
return {'cnt': 0, 'total_amount': 0}
|
||||
|
||||
|
||||
async def get_customer_daily_trend(request):
|
||||
"""获取当前客户组织当月每日调用趋势(每天的调用次数和金额)"""
|
||||
env = request._run_ns
|
||||
org_id = await _get_org_id(request)
|
||||
now = datetime.now()
|
||||
month_start = now.strftime('%Y-%m-01')
|
||||
if now.month == 12:
|
||||
month_end = f'{now.year + 1}-01-01'
|
||||
else:
|
||||
month_end = f'{now.year}-{now.month + 1:02d}-01'
|
||||
async with get_sor_context(env, 'sage') as sor:
|
||||
sql = """
|
||||
SELECT
|
||||
a.use_date as date,
|
||||
COUNT(*) as cnt,
|
||||
COALESCE(SUM(a.amount), 0) as total_amount
|
||||
FROM llmusage a
|
||||
WHERE a.use_date >= ${month_start}$
|
||||
AND a.use_date < ${month_end}$
|
||||
AND a.userorgid = ${org_id}$
|
||||
GROUP BY a.use_date
|
||||
ORDER BY a.use_date ASC
|
||||
"""
|
||||
recs = await sor.sqlExe(sql, {
|
||||
'month_start': month_start,
|
||||
'month_end': month_end,
|
||||
'org_id': org_id
|
||||
})
|
||||
result = []
|
||||
for r in recs:
|
||||
result.append({
|
||||
'date': r.get('date', ''),
|
||||
'cnt': int(r.get('cnt', 0)),
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 4)
|
||||
'total_amount': round(float(r.get('total_amount', 0)), 2)
|
||||
})
|
||||
return result
|
||||
|
||||
@ -591,8 +213,6 @@ def load_dashboard():
|
||||
g = ServerEnv()
|
||||
g.get_today_usage = get_today_usage
|
||||
g.get_today_amount = get_today_amount
|
||||
g.get_usage_trend = get_usage_trend
|
||||
g.get_amount_trend = get_amount_trend
|
||||
g.get_total_users = get_total_users
|
||||
g.get_concurrent_users = get_concurrent_users
|
||||
g.get_top_models = get_top_models
|
||||
@ -601,15 +221,3 @@ 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_top_users_combined = get_top_users_combined
|
||||
g.get_top_providers_combined = get_top_providers_combined
|
||||
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
|
||||
g.get_user_today_models = get_user_today_models
|
||||
g.get_all_today_models = get_all_today_models
|
||||
g.get_customer_daily_models = get_customer_daily_models
|
||||
g.get_customer_monthly_models = get_customer_monthly_models
|
||||
g.get_customer_daily_summary = get_customer_daily_summary
|
||||
g.get_customer_month_summary = get_customer_month_summary
|
||||
g.get_customer_daily_trend = get_customer_daily_trend
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
Cancel: Cancel
|
||||
Conform: Confirm
|
||||
Discard: Discard
|
||||
Reset: Reset
|
||||
Submit: Submit
|
||||
今日交易金额: Today's Transaction Amount
|
||||
今日各模型调用: Today's Model Calls
|
||||
今日活跃用户: Today's Active Users
|
||||
今日消费金额: Today's Spending Amount
|
||||
今日调用次数: Today's Call Count
|
||||
今日调用笔数: Today's Transaction Count
|
||||
供应商排行: Supplier Ranking
|
||||
刷新: Refresh
|
||||
在线用户: Online Users
|
||||
客户专属监控: Customer Dedicated Monitoring
|
||||
当前并发用户: Current Concurrent Users
|
||||
我的今日模型使用: My Today's Model Usage
|
||||
排名: Ranking
|
||||
数据概览: Data Overview
|
||||
数据看板: Data Dashboard
|
||||
暂无数据: No Data Available
|
||||
本月各模型调用: This Month's Model Calls
|
||||
本月新增用户: This Month's New Users
|
||||
本月每日调用趋势: This Month's Daily Call Trend
|
||||
本月消费金额: This Month's Spending Amount
|
||||
本月调用次数: This Month's Call Count
|
||||
查看本组织各模型每日/每月调用次数与金额统计: View Daily/Monthly Call Count and Amount Statistics for Each Model in Your Organization
|
||||
热门模型: Popular Models
|
||||
用户: User
|
||||
用户总数: Total Users
|
||||
用户排行: User Ranking
|
||||
用户金额 TOP 5(今日): User Amount TOP 5 (Today)
|
||||
笔数: Transactions
|
||||
组织机构数: Number of Organizations
|
||||
记账异常: Billing Exception
|
||||
记账错误笔数: Billing Error Count
|
||||
返回首页: Back to Home
|
||||
金额: Amount
|
||||
@ -1,38 +0,0 @@
|
||||
Cancel: キャンセル
|
||||
Conform: 確認
|
||||
Discard: 破棄
|
||||
Reset: リセット
|
||||
Submit: 送信
|
||||
今日交易金额: 今日の取引金額
|
||||
今日各模型调用: 今日の各モデル呼び出し
|
||||
今日活跃用户: 今日のアクティブユーザー
|
||||
今日消费金额: 今日の消費金額
|
||||
今日调用次数: 今日の呼び出し回数
|
||||
今日调用笔数: 今日の取引件数
|
||||
供应商排行: サプライヤーランキング
|
||||
刷新: 更新
|
||||
在线用户: オンラインユーザー
|
||||
客户专属监控: お客様専用モニタリング
|
||||
当前并发用户: 現在の同時接続ユーザー
|
||||
我的今日模型使用: 今日のモデル使用状況
|
||||
排名: ランキング
|
||||
数据概览: データ概要
|
||||
数据看板: データダッシュボード
|
||||
暂无数据: データなし
|
||||
本月各模型调用: 今月の各モデル呼び出し
|
||||
本月新增用户: 今月の新規ユーザー
|
||||
本月每日调用趋势: 今月の日次呼び出しトレンド
|
||||
本月消费金额: 今月の消費金額
|
||||
本月调用次数: 今月の呼び出し回数
|
||||
查看本组织各模型每日/每月调用次数与金额统计: 組織内の各モデルの日次/月次呼び出し回数と金額統計を表示
|
||||
热门模型: 人気のモデル
|
||||
用户: ユーザー
|
||||
用户总数: ユーザー総数
|
||||
用户排行: ユーザーランキング
|
||||
用户金额 TOP 5(今日): ユーザー金額 TOP 5(今日)
|
||||
笔数: 取引件数
|
||||
组织机构数: 組織数
|
||||
记账异常: 請求異常
|
||||
记账错误笔数: 請求エラー件数
|
||||
返回首页: ホームに戻る
|
||||
金额: 金額
|
||||
@ -1,38 +0,0 @@
|
||||
Cancel: 취소
|
||||
Conform: 확인
|
||||
Discard: 폐기
|
||||
Reset: 초기화
|
||||
Submit: 제출
|
||||
今日交易金额: 오늘 거래 금액
|
||||
今日各模型调用: 오늘 각 모델 호출
|
||||
今日活跃用户: 오늘 활성 사용자
|
||||
今日消费金额: 오늘 소비 금액
|
||||
今日调用次数: 오늘 호출 횟수
|
||||
今日调用笔数: 오늘 거래 건수
|
||||
供应商排行: 공급업체 순위
|
||||
刷新: 새로고침
|
||||
在线用户: 온라인 사용자
|
||||
客户专属监控: 고객 전용 모니터링
|
||||
当前并发用户: 현재 동시 접속 사용자
|
||||
我的今日模型使用: 나의 오늘 모델 사용량
|
||||
排名: 순위
|
||||
数据概览: 데이터 개요
|
||||
数据看板: 데이터 대시보드
|
||||
暂无数据: 데이터 없음
|
||||
本月各模型调用: 이번 달 각 모델 호출
|
||||
本月新增用户: 이번 달 신규 사용자
|
||||
本月每日调用趋势: 이번 달 일별 호출 추세
|
||||
本月消费金额: 이번 달 소비 금액
|
||||
本月调用次数: 이번 달 호출 횟수
|
||||
查看本组织各模型每日/每月调用次数与金额统计: 조직 내 각 모델의 일별/월별 호출 횟수 및 금액 통계 보기
|
||||
热门模型: 인기 모델
|
||||
用户: 사용자
|
||||
用户总数: 전체 사용자 수
|
||||
用户排行: 사용자 순위
|
||||
用户金额 TOP 5(今日): 사용자 금액 TOP 5 (오늘)
|
||||
笔数: 거래 건수
|
||||
组织机构数: 조직 수
|
||||
记账异常: 과금 이상
|
||||
记账错误笔数: 과금 오류 건수
|
||||
返回首页: 홈으로 돌아가기
|
||||
金额: 금액
|
||||
@ -1,38 +0,0 @@
|
||||
Cancel: Cancel
|
||||
Conform: Conform
|
||||
Discard: Discard
|
||||
Reset: Reset
|
||||
Submit: Submit
|
||||
今日交易金额: 今日交易金额
|
||||
今日各模型调用: 今日各模型调用
|
||||
今日活跃用户: 今日活跃用户
|
||||
今日消费金额: 今日消费金额
|
||||
今日调用次数: 今日调用次数
|
||||
今日调用笔数: 今日调用笔数
|
||||
供应商排行: 供应商排行
|
||||
刷新: 刷新
|
||||
在线用户: 在线用户
|
||||
客户专属监控: 客户专属监控
|
||||
当前并发用户: 当前并发用户
|
||||
我的今日模型使用: 我的今日模型使用
|
||||
排名: 排名
|
||||
数据概览: 数据概览
|
||||
数据看板: 数据看板
|
||||
暂无数据: 暂无数据
|
||||
本月各模型调用: 本月各模型调用
|
||||
本月新增用户: 本月新增用户
|
||||
本月每日调用趋势: 本月每日调用趋势
|
||||
本月消费金额: 本月消费金额
|
||||
本月调用次数: 本月调用次数
|
||||
查看本组织各模型每日/每月调用次数与金额统计: 查看本组织各模型每日/每月调用次数与金额统计
|
||||
热门模型: 热门模型
|
||||
用户: 用户
|
||||
用户总数: 用户总数
|
||||
用户排行: 用户排行
|
||||
用户金额 TOP 5(今日): 用户金额 TOP 5(今日)
|
||||
笔数: 笔数
|
||||
组织机构数: 组织机构数
|
||||
记账异常: 记账异常
|
||||
记账错误笔数: 记账错误笔数
|
||||
返回首页: 返回首页
|
||||
金额: 金额
|
||||
@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
xls2ui -m ../models -o ../wwwroot dashboard_for_sage *.json
|
||||
@ -1,158 +0,0 @@
|
||||
"""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"),
|
||||
|
||||
# API doc
|
||||
("/dashboard_for_sage/api_doc.ui", "logined"),
|
||||
("/dashboard_for_sage/api_doc.md", "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())
|
||||
@ -12,11 +12,10 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "记账错误笔数",
|
||||
"fontSize": "14px",
|
||||
"color": "#888",
|
||||
"marginBottom": "8px",
|
||||
"otext": "记账错误笔数",
|
||||
"i18n": true
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -30,4 +29,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"widgettype": "ChartBar",
|
||||
"options": {
|
||||
"height": "280px",
|
||||
"width": "100%",
|
||||
"data_url": "{{entire_url('api/all_today_models.dspy')}}",
|
||||
"nameField": "model_name",
|
||||
"valueFields": ["cnt", "total_amount"],
|
||||
"refresh_period": 30
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
# coding=utf-8
|
||||
"""All users' today model usage data API for ChartBar"""
|
||||
import json
|
||||
|
||||
models = await get_all_today_models(request)
|
||||
return json.dumps(models, ensure_ascii=False, default=str)
|
||||
@ -1,6 +0,0 @@
|
||||
# coding=utf-8
|
||||
"""Customer daily per-model usage data API for ChartBar"""
|
||||
import json
|
||||
|
||||
models = await get_customer_daily_models(request)
|
||||
return json.dumps(models, ensure_ascii=False, default=str)
|
||||
@ -1,6 +0,0 @@
|
||||
# coding=utf-8
|
||||
"""Customer daily trend data API for ChartLine (current month)"""
|
||||
import json
|
||||
|
||||
trend = await get_customer_daily_trend(request)
|
||||
return json.dumps(trend, ensure_ascii=False, default=str)
|
||||
@ -1,6 +0,0 @@
|
||||
# coding=utf-8
|
||||
"""Customer monthly per-model usage data API for ChartBar"""
|
||||
import json
|
||||
|
||||
models = await get_customer_monthly_models(request)
|
||||
return json.dumps(models, ensure_ascii=False, default=str)
|
||||
@ -3,4 +3,4 @@
|
||||
import json
|
||||
|
||||
models = await get_top_models(request)
|
||||
return json.dumps(models, ensure_ascii=False, default=str)
|
||||
print(json.dumps(models))
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
# coding=utf-8
|
||||
"""Top providers data API for ChartBar"""
|
||||
import json
|
||||
|
||||
providers = await get_top_providers_combined(request)
|
||||
return json.dumps(providers, ensure_ascii=False, default=str)
|
||||
@ -1,6 +0,0 @@
|
||||
# coding=utf-8
|
||||
"""Top users data API for ChartBar"""
|
||||
import json
|
||||
|
||||
users = await get_top_users_combined(request)
|
||||
return json.dumps(users, ensure_ascii=False, default=str)
|
||||
@ -1,6 +0,0 @@
|
||||
# coding=utf-8
|
||||
"""User's today model usage data API for ChartBar"""
|
||||
import json
|
||||
|
||||
models = await get_user_today_models(request)
|
||||
return json.dumps(models, ensure_ascii=False, default=str)
|
||||
1783
wwwroot/api_doc.md
1783
wwwroot/api_doc.md
File diff suppressed because it is too large
Load Diff
@ -1,7 +0,0 @@
|
||||
{
|
||||
"widgettype": "ApiDoc",
|
||||
"options": {
|
||||
"md_url": "{{entire_url('api_doc.md')}}",
|
||||
"height": "100%"
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"widgettype": "ChartBar",
|
||||
"options": {
|
||||
"height": "250px",
|
||||
"height": "280px",
|
||||
"width": "100%",
|
||||
"data_url": "{{entire_url('api/top_models.dspy')}}",
|
||||
"nameField": "model_name",
|
||||
"valueFields": ["total_amount", "cnt"]
|
||||
"valueFields": ["cnt", "total_amount"]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"widgettype": "ChartBar",
|
||||
"options": {
|
||||
"height": "250px",
|
||||
"data_url": "{{entire_url('api/top_providers.dspy')}}",
|
||||
"nameField": "provider_name",
|
||||
"valueFields": ["total_amount", "cnt"]
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"widgettype": "ChartBar",
|
||||
"options": {
|
||||
"height": "250px",
|
||||
"data_url": "{{entire_url('api/top_users.dspy')}}",
|
||||
"nameField": "user_name",
|
||||
"valueFields": ["total_amount", "cnt"]
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"bgcolor": "var(--sage-bg-card, #FFFFFF)",
|
||||
"bgcolor": "#FFFFFF",
|
||||
"padding": "24px",
|
||||
"borderRadius": "8px",
|
||||
"flex": "1",
|
||||
@ -12,11 +12,10 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "当前并发用户",
|
||||
"fontSize": "14px",
|
||||
"color": "#888",
|
||||
"marginBottom": "8px",
|
||||
"otext": "当前并发用户",
|
||||
"i18n": true
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -30,4 +29,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"widgettype": "ChartBar",
|
||||
"options": {
|
||||
"height": "300px",
|
||||
"width": "100%",
|
||||
"data_url": "{{entire_url('api/customer_daily_models.dspy')}}",
|
||||
"nameField": "model_name",
|
||||
"valueFields": ["cnt", "total_amount"],
|
||||
"refresh_period": 60
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"widgettype": "ChartLine",
|
||||
"options": {
|
||||
"height": "280px",
|
||||
"width": "100%",
|
||||
"data_url": "{{entire_url('api/customer_daily_trend.dspy')}}",
|
||||
"nameField": "date",
|
||||
"valueFields": ["cnt", "total_amount"],
|
||||
"refresh_period": 120
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"widgettype": "ChartBar",
|
||||
"options": {
|
||||
"height": "300px",
|
||||
"width": "100%",
|
||||
"data_url": "{{entire_url('api/customer_monthly_models.dspy')}}",
|
||||
"nameField": "model_name",
|
||||
"valueFields": ["cnt", "total_amount"],
|
||||
"refresh_period": 120
|
||||
}
|
||||
}
|
||||
@ -1,462 +0,0 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {
|
||||
"css": "filler"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"marginBottom": "24px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title2",
|
||||
"options": {
|
||||
"fontWeight": "700",
|
||||
"otext": "客户专属监控",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "返回首页",
|
||||
"borderRadius": "6px",
|
||||
"padding": "6px 16px",
|
||||
"fontSize": "13px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.sage_main_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/dashboard_for_sage/index.ui')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {
|
||||
"gap": "16px",
|
||||
"minWidth": "220px",
|
||||
"marginBottom": "24px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"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=\"currentColor\" stroke-width=\"2\"><path d=\"M3 3v18h18\"/><path d=\"M18 17V9\"/><path d=\"M13 17V5\"/><path d=\"M8 17v-3\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "{{get_customer_daily_summary(request).cnt}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-label",
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "今日调用次数",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"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=\"currentColor\" stroke-width=\"2\"><path d=\"M12 6v12m6-6H6\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "¥{{get_customer_daily_summary(request).total_amount|round(2)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-label",
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "今日消费金额",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"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=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "{{get_customer_month_summary(request).cnt}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-label",
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "本月调用次数",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"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=\"currentColor\" stroke-width=\"2\"><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"23\"/><path d=\"M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "¥{{get_customer_month_summary(request).total_amount|round(2)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-label",
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "本月消费金额",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"width": "100%",
|
||||
"borderRadius": "12px",
|
||||
"padding": "20px",
|
||||
"marginBottom": "20px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"marginBottom": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"fontWeight": "600",
|
||||
"otext": "本月每日调用趋势",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "刷新",
|
||||
"border": "none",
|
||||
"borderRadius": "6px",
|
||||
"padding": "4px 12px",
|
||||
"fontSize": "12px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "method",
|
||||
"target": "-@ChartLine",
|
||||
"method": "render_urldata",
|
||||
"params": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"url": "{{entire_url('customer_daily_trend.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"gap": "20px",
|
||||
"marginBottom": "20px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"width": "50%",
|
||||
"borderRadius": "12px",
|
||||
"padding": "20px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"marginBottom": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"fontWeight": "600",
|
||||
"otext": "今日各模型调用",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "刷新",
|
||||
"border": "none",
|
||||
"borderRadius": "6px",
|
||||
"padding": "4px 12px",
|
||||
"fontSize": "12px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "method",
|
||||
"target": "-@ChartBar",
|
||||
"method": "render_urldata",
|
||||
"params": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"url": "{{entire_url('customer_daily_chart.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"width": "50%",
|
||||
"borderRadius": "12px",
|
||||
"padding": "20px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"marginBottom": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"fontWeight": "600",
|
||||
"otext": "本月各模型调用",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "刷新",
|
||||
"border": "none",
|
||||
"borderRadius": "6px",
|
||||
"padding": "4px 12px",
|
||||
"fontSize": "12px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "method",
|
||||
"target": "-@ChartBar",
|
||||
"method": "render_urldata",
|
||||
"params": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"url": "{{entire_url('customer_monthly_chart.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
58
wwwroot/global_menu.ui
Normal file
58
wwwroot/global_menu.ui
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
481
wwwroot/index.ui
481
wwwroot/index.ui
@ -1,33 +1,24 @@
|
||||
{% set roles = get_user_roles(get_user()) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "dashboard_root",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"bgcolor": "var(--sage-bg-primary, transparent)"
|
||||
"height": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VScrollPanel",
|
||||
"options": {
|
||||
"css": "filler"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"marginBottom": "12px"
|
||||
"marginBottom": "24px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title2",
|
||||
"options": {
|
||||
"fontWeight": "700",
|
||||
"otext": "数据概览",
|
||||
"i18n": true
|
||||
"text": "数据概览",
|
||||
"color": "#F1F5F9",
|
||||
"fontWeight": "700"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -36,40 +27,19 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{get_today_usage(request) and request._run_ns.curDateString() or ''}}",
|
||||
"fontSize": "13px"
|
||||
"text": "最后更新: {{get_today_usage(request) and request._run_ns.curDateString() or ''}}",
|
||||
"fontSize": "13px",
|
||||
"color": "#64748B"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "⛶",
|
||||
"border": "none",
|
||||
"borderRadius": "6px",
|
||||
"padding": "4px 12px",
|
||||
"fontSize": "16px",
|
||||
"marginLeft": "12px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "var el = document.getElementById('dashboard_root'); if (!document.fullscreenElement) { (el || document.documentElement).requestFullscreen(); } else { document.exitFullscreen(); }"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{% if 'owner.*' in roles or 'reseller.*' in roles %}
|
||||
,{
|
||||
"widgettype": "HBox",
|
||||
},
|
||||
{
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"gap": "16px",
|
||||
"marginBottom": "16px"
|
||||
"minWidth": "220px",
|
||||
"marginBottom": "24px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
@ -77,8 +47,7 @@
|
||||
"id": "stat_today_usage",
|
||||
"options": {
|
||||
"period_seconds": 30,
|
||||
"url": "{{entire_url('stat_today_usage.ui')}}",
|
||||
"width": "50%"
|
||||
"url": "{{entire_url('stat_today_usage.ui')}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -86,36 +55,15 @@
|
||||
"id": "stat_today_amount",
|
||||
"options": {
|
||||
"period_seconds": 30,
|
||||
"url": "{{entire_url('stat_today_amount.ui')}}",
|
||||
"width": "50%"
|
||||
"url": "{{entire_url('stat_today_amount.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"gap": "16px",
|
||||
"marginBottom": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
},
|
||||
{
|
||||
"widgettype": "RefreshWidget",
|
||||
"id": "stat_total_users",
|
||||
"options": {
|
||||
"period_seconds": 60,
|
||||
"url": "{{entire_url('stat_total_users.ui')}}",
|
||||
"width": "25%"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "RefreshWidget",
|
||||
"id": "stat_active_users",
|
||||
"options": {
|
||||
"period_seconds": 60,
|
||||
"url": "{{entire_url('stat_active_users.ui')}}",
|
||||
"width": "25%"
|
||||
"url": "{{entire_url('stat_total_users.ui')}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -123,8 +71,7 @@
|
||||
"id": "stat_concurrent",
|
||||
"options": {
|
||||
"period_seconds": 15,
|
||||
"url": "{{entire_url('stat_concurrent.ui')}}",
|
||||
"width": "25%"
|
||||
"url": "{{entire_url('stat_concurrent.ui')}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -132,8 +79,7 @@
|
||||
"id": "stat_errors",
|
||||
"options": {
|
||||
"period_seconds": 30,
|
||||
"url": "{{entire_url('stat_errors.ui')}}",
|
||||
"width": "25%"
|
||||
"url": "{{entire_url('stat_errors.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -142,17 +88,18 @@
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"gap": "16px",
|
||||
"gap": "20px",
|
||||
"height": "auto"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"width": "33%",
|
||||
"width": "60%",
|
||||
"bgcolor": "#1E293B",
|
||||
"borderRadius": "12px",
|
||||
"padding": "16px"
|
||||
"padding": "20px",
|
||||
"border": "1px solid #334155"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
@ -160,17 +107,41 @@
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"marginBottom": "12px"
|
||||
"marginBottom": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "12px",
|
||||
"otext": "热门模型",
|
||||
"i18n": true
|
||||
"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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -187,202 +158,220 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"width": "33%",
|
||||
"width": "40%",
|
||||
"bgcolor": "#1E293B",
|
||||
"borderRadius": "12px",
|
||||
"padding": "16px"
|
||||
"padding": "20px",
|
||||
"border": "1px solid #334155"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "快捷入口",
|
||||
"color": "#F1F5F9",
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "12px",
|
||||
"otext": "用户排行",
|
||||
"i18n": true
|
||||
"marginBottom": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "RefreshWidget",
|
||||
"id": "chart_top_users",
|
||||
"widgettype": "ResponsableBox",
|
||||
"options": {
|
||||
"period_seconds": 60,
|
||||
"url": "{{entire_url('chart_top_users.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"width": "33%",
|
||||
"borderRadius": "12px",
|
||||
"padding": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "12px",
|
||||
"otext": "供应商排行",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "RefreshWidget",
|
||||
"id": "chart_top_providers",
|
||||
"options": {
|
||||
"period_seconds": 60,
|
||||
"url": "{{entire_url('chart_top_providers.ui')}}"
|
||||
}
|
||||
"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": "<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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "<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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "<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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": "<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>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "异常记录",
|
||||
"color": "#E2E8F0",
|
||||
"fontSize": "13px",
|
||||
"marginTop": "8px"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endif %}
|
||||
{% if 'customer.*' in roles %}
|
||||
,{
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"width": "100%",
|
||||
"bgcolor": "#1E293B",
|
||||
"borderRadius": "12px",
|
||||
"padding": "20px",
|
||||
"border": "1px solid #334155",
|
||||
"marginTop": "20px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center",
|
||||
"text": "用户消费排行(Top 5)",
|
||||
"color": "#F1F5F9",
|
||||
"fontWeight": "600",
|
||||
"marginBottom": "16px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"fontWeight": "600",
|
||||
"otext": "我的今日模型使用",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"options": {
|
||||
"label": "刷新",
|
||||
"border": "none",
|
||||
"borderRadius": "6px",
|
||||
"padding": "4px 12px",
|
||||
"fontSize": "12px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "method",
|
||||
"target": "-@ChartBar",
|
||||
"method": "render_urldata",
|
||||
"params": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"widgettype": "RefreshWidget",
|
||||
"id": "table_top_users",
|
||||
"options": {
|
||||
"url": "{{entire_url('user_today_models_chart.ui')}}"
|
||||
"period_seconds": 30,
|
||||
"url": "{{entire_url('table_top_users.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endif %}
|
||||
{% if 'owner.*' not in roles and 'reseller.*' not in roles and 'customer.*' in roles %}
|
||||
,{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "card",
|
||||
"width": "100%",
|
||||
"borderRadius": "12px",
|
||||
"padding": "20px",
|
||||
"marginTop": "20px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.sage_main_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/dashboard_for_sage/customer_usage.ui')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"alignItems": "center"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M3 3v18h18\"/><path d=\"M18 17V9\"/><path d=\"M13 17V5\"/><path d=\"M8 17v-3\"/></svg>",
|
||||
"width": "28px",
|
||||
"height": "28px",
|
||||
"marginRight": "12px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"fontWeight": "600",
|
||||
"otext": "客户专属监控",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"fontSize": "13px",
|
||||
"otext": "查看本组织各模型每日/每月调用次数与金额统计",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"9 18 15 12 9 6\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endif %}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
147
wwwroot/shell.ui
Normal file
147
wwwroot/shell.ui
Normal file
@ -0,0 +1,147 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"bgcolor": "#0B1120"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Html",
|
||||
"options": {
|
||||
"html": "<link rel=\"stylesheet\" href=\"/dashboard_for_sage/shell_theme.css\"><script src=\"/dashboard_for_sage/shell_theme.js\"><\\/script>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "56px",
|
||||
"bgcolor": "#111827",
|
||||
"borderBottom": "1px solid #334155",
|
||||
"padding": "0 16px",
|
||||
"alignItems": "center"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"id": "sidebar_toggle_btn",
|
||||
"options": {
|
||||
"label": "",
|
||||
"bgcolor": "transparent",
|
||||
"color": "#94A3B8",
|
||||
"border": "1px solid #334155",
|
||||
"borderRadius": "8px",
|
||||
"width": "36px",
|
||||
"height": "36px",
|
||||
"padding": "0"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "sageToggleSidebar()"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Image",
|
||||
"options": {
|
||||
"url": "{{entire_url('/imgs/msp.png')}}",
|
||||
"height": "32px",
|
||||
"marginLeft": "12px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Title4",
|
||||
"options": {
|
||||
"text": "Sage",
|
||||
"color": "#F1F5F9",
|
||||
"fontWeight": "bold",
|
||||
"marginLeft": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "Button",
|
||||
"id": "theme_toggle_btn",
|
||||
"options": {
|
||||
"label": "",
|
||||
"bgcolor": "transparent",
|
||||
"color": "#94A3B8",
|
||||
"border": "1px solid #334155",
|
||||
"borderRadius": "50%",
|
||||
"width": "36px",
|
||||
"height": "36px",
|
||||
"padding": "0"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "script",
|
||||
"target": "self",
|
||||
"script": "sageToggleTheme()"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"url": "{{entire_url('/rbac/user/user_panel.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "calc(100% - 56px)"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "sage_sidebar",
|
||||
"options": {
|
||||
"width": "240px",
|
||||
"height": "100%",
|
||||
"bgcolor": "#111827",
|
||||
"borderRight": "1px solid #334155"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "urlwidget",
|
||||
"options": {
|
||||
"url": "{{entire_url('global_menu.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"id": "sage_main_content",
|
||||
"options": {
|
||||
"css": "filler",
|
||||
"height": "100%",
|
||||
"padding": "24px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "加载中...",
|
||||
"fontSize": "16px",
|
||||
"color": "#94A3B8"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -78,17 +78,6 @@ body {
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
/* Ensure theme variables override bricks.css hardcoded values */
|
||||
[data-theme="dark"] body {
|
||||
background-color: var(--sage-bg-primary);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] body {
|
||||
background-color: var(--sage-bg-primary);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
/* ===== Shell Layout ===== */
|
||||
.sage-shell {
|
||||
width: 100%;
|
||||
@ -96,9 +85,6 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: var(--sage-bg-primary);
|
||||
color: var(--sage-text-primary);
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.sage-topbar {
|
||||
@ -128,9 +114,7 @@ body {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex-shrink: 0;
|
||||
transition: width 0.3s ease, transform 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 50;
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
|
||||
.sage-sidebar.collapsed {
|
||||
@ -143,61 +127,12 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Menu collapsed state - hide text labels, center icons */
|
||||
.menu-collapsed .filler {
|
||||
display: none !important;
|
||||
}
|
||||
.menu-collapsed .menuitem {
|
||||
justify-content: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.menu-collapsed .menuitem:hover {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
/* Mobile: sidebar as overlay */
|
||||
@media (max-width: 768px) {
|
||||
.sage-sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: var(--sage-topbar-height);
|
||||
height: calc(100vh - var(--sage-topbar-height));
|
||||
transform: translateX(0);
|
||||
box-shadow: var(--sage-shadow-lg);
|
||||
}
|
||||
|
||||
.sage-sidebar.collapsed {
|
||||
transform: translateX(-100%);
|
||||
width: var(--sage-sidebar-width); /* Keep full width when hidden on mobile */
|
||||
}
|
||||
|
||||
.sage-main {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sage-main {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 24px;
|
||||
background-color: var(--sage-bg-primary);
|
||||
min-width: 0; /* Prevent flex item from overflowing */
|
||||
min-height: 0; /* Allow flex children to shrink below content size */
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
/* Responsive padding for mobile */
|
||||
@media (max-width: 768px) {
|
||||
.sage-main {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.sage-main {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Stat Cards ===== */
|
||||
@ -386,390 +321,40 @@ body {
|
||||
}
|
||||
|
||||
/* ===== DataViewer Overrides for theme ===== */
|
||||
.tabular-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .dataviewer-toolbar,
|
||||
[data-theme="dark"] .tabular,
|
||||
[data-theme="dark"] .data-row,
|
||||
[data-theme="dark"] body {
|
||||
background-color: var(--sage-bg-primary);
|
||||
[data-theme="dark"] .data-row {
|
||||
background-color: var(--sage-bg-card);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tabular-header-row {
|
||||
[data-theme="dark"] .tabular th {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
color: var(--sage-text-secondary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tabular-row {
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tabular-row:nth-child(odd) {
|
||||
background-color: var(--sage-bg-card);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tabular-row:nth-child(even) {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tabular-row-selected {
|
||||
background-color: var(--sage-bg-hover) !important;
|
||||
}
|
||||
[data-theme="dark"] .tabular-row-selected,
|
||||
[data-theme="dark"] .tabular-row-selected .tabular-cell {
|
||||
color: var(--sage-brand) !important;
|
||||
}
|
||||
|
||||
/* Light theme selected row */
|
||||
[data-theme="light"] .tabular-row-selected {
|
||||
background-color: #E0F2FE !important;
|
||||
}
|
||||
[data-theme="light"] .tabular-row-selected,
|
||||
[data-theme="light"] .tabular-row-selected .tabular-cell {
|
||||
color: #0369A1 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tabular-cell {
|
||||
[data-theme="dark"] .tabular td {
|
||||
border-color: var(--sage-border-weak);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .popup,
|
||||
[data-theme="dark"] .modal,
|
||||
[data-theme="dark"] .message {
|
||||
background-color: var(--sage-bg-card);
|
||||
color: var(--sage-text-primary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .titlebar {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .inputbox {
|
||||
background-color: var(--sage-bg-input);
|
||||
color: var(--sage-text-primary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .htoolbar,
|
||||
[data-theme="dark"] .vtoolbar {
|
||||
background-color: var(--sage-bg-toolbar);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .accordion-item {
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .accordion-item:nth-child(odd) {
|
||||
background-color: var(--sage-bg-card);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .accordion-item:nth-child(even) {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .accordion-item-header {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .accordion-item-info {
|
||||
background-color: var(--sage-bg-card);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .accordion-item-selected {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .accordion-item-info-selected {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .card {
|
||||
background-color: var(--sage-bg-card);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .subcard {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .curpos {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .selected {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tabpanel {
|
||||
background-color: var(--sage-bg-card);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tabpanel-content {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .toolbar-button {
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .toolbar-button-active {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .auto-textarea {
|
||||
background-color: var(--sage-bg-input);
|
||||
color: var(--sage-text-primary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .droparea {
|
||||
border-color: var(--sage-border-primary);
|
||||
color: var(--sage-text-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .droparea:hover {
|
||||
border-color: var(--sage-brand);
|
||||
color: var(--sage-brand);
|
||||
background: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .thinking-content {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .resp-error {
|
||||
background-color: rgba(239, 68, 68, 0.15);
|
||||
color: var(--sage-danger);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .resp-content {
|
||||
background-color: var(--sage-bg-card);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .llm_title {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .progress-container {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] pre {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .llm_msg {
|
||||
background-color: var(--sage-bg-card);
|
||||
color: var(--sage-text-primary);
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .user_msg {
|
||||
background-color: var(--sage-bg-hover);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .dataviewer-toolbar,
|
||||
[data-theme="light"] .tabular,
|
||||
[data-theme="light"] .data-row,
|
||||
[data-theme="light"] body {
|
||||
[data-theme="light"] .data-row {
|
||||
background-color: var(--sage-bg-card);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabular-header-row {
|
||||
[data-theme="light"] .tabular th {
|
||||
background-color: var(--sage-bg-hover);
|
||||
color: var(--sage-text-secondary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabular-row {
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabular-row:nth-child(odd) {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabular-row:nth-child(even) {
|
||||
background-color: var(--sage-bg-card);
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabular-row-selected {
|
||||
color: var(--sage-brand-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabular-cell {
|
||||
[data-theme="light"] .tabular td {
|
||||
border-color: var(--sage-border-weak);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .popup,
|
||||
[data-theme="light"] .modal,
|
||||
[data-theme="light"] .message {
|
||||
background-color: var(--sage-bg-card);
|
||||
color: var(--sage-text-primary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .titlebar {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .inputbox {
|
||||
background-color: var(--sage-bg-input);
|
||||
color: var(--sage-text-primary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .htoolbar,
|
||||
[data-theme="light"] .vtoolbar {
|
||||
background-color: var(--sage-bg-toolbar);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .accordion-item {
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .accordion-item:nth-child(odd) {
|
||||
background-color: var(--sage-bg-card);
|
||||
}
|
||||
|
||||
[data-theme="light"] .accordion-item:nth-child(even) {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
[data-theme="light"] .accordion-item-header {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .accordion-item-info {
|
||||
background-color: var(--sage-bg-card);
|
||||
}
|
||||
|
||||
[data-theme="light"] .accordion-item-selected {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .accordion-item-info-selected {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .card {
|
||||
background-color: var(--sage-bg-card);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .subcard {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
[data-theme="light"] .curpos {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .selected {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabpanel {
|
||||
background-color: var(--sage-bg-card);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabpanel-content {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .toolbar-button {
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .toolbar-button-active {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .auto-textarea {
|
||||
background-color: var(--sage-bg-input);
|
||||
color: var(--sage-text-primary);
|
||||
border-color: var(--sage-border-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .droparea {
|
||||
border-color: var(--sage-border-primary);
|
||||
color: var(--sage-text-secondary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .droparea:hover {
|
||||
border-color: var(--sage-brand);
|
||||
color: var(--sage-brand);
|
||||
background: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .thinking-content {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .resp-error {
|
||||
background-color: rgba(239, 68, 68, 0.1);
|
||||
color: var(--sage-danger);
|
||||
}
|
||||
|
||||
[data-theme="light"] .resp-content {
|
||||
background-color: var(--sage-bg-card);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .llm_title {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] .progress-container {
|
||||
background-color: var(--sage-bg-hover);
|
||||
}
|
||||
|
||||
[data-theme="light"] pre {
|
||||
background-color: var(--sage-bg-secondary);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .llm_msg {
|
||||
background-color: var(--sage-bg-card);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .user_msg {
|
||||
background-color: var(--sage-bg-hover);
|
||||
color: var(--sage-text-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .tabular { background-color: #f5f5f5; color: #1E293B; }
|
||||
[data-theme="light"] .tabular-header-row { background-color: #E2E8F0; color: #0F172A; font-weight: 600; }
|
||||
[data-theme="light"] .tabular-header-row .tabular-cell { color: #0F172A; }
|
||||
[data-theme="light"] .tabular-row { color: #1E293B; }
|
||||
[data-theme="light"] .tabular-row:nth-child(odd) { background-color: #FFFFFF; }
|
||||
[data-theme="light"] .tabular-row:nth-child(even) { background-color: #F8FAFC; }
|
||||
[data-theme="light"] .tabular-cell { border-color: #E2E8F0; color: #1E293B; }
|
||||
[data-theme="light"] .card { color: #1E293B; }
|
||||
[data-theme="light"] .inputbox { color: #1E293B; background-color: #FFFFFF; }
|
||||
[data-theme="light"] .popup, [data-theme="light"] .modal { color: #1E293B; }
|
||||
[data-theme="light"] .accordion-item-info { color: #475569; }
|
||||
[data-theme="light"] .toolbar-button { color: #334155; }
|
||||
[data-theme="light"] .message { color: #1E293B; }
|
||||
|
||||
/* ===== Menu Overrides ===== */
|
||||
[data-theme="dark"] .menu-item {
|
||||
color: var(--sage-text-secondary);
|
||||
@ -791,53 +376,18 @@ body {
|
||||
color: var(--sage-brand);
|
||||
}
|
||||
|
||||
/* ===== Utility Classes ===== */
|
||||
.sage-brand-title {
|
||||
color: var(--sage-text-primary);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sage-text-secondary {
|
||||
color: var(--sage-text-secondary);
|
||||
}
|
||||
|
||||
/* ===== Responsive - Topbar ===== */
|
||||
/* ===== Responsive ===== */
|
||||
@media (max-width: 768px) {
|
||||
.sage-topbar {
|
||||
padding: 0 8px;
|
||||
gap: 6px;
|
||||
.sage-sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: var(--sage-topbar-height);
|
||||
bottom: 0;
|
||||
z-index: 200;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
.sage-brand-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.sage-brand-title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== Responsive - Stat Cards ===== */
|
||||
@media (max-width: 768px) {
|
||||
.stat-card {
|
||||
padding: 14px;
|
||||
}
|
||||
.stat-card .stat-value {
|
||||
font-size: 22px;
|
||||
}
|
||||
.stat-card .stat-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
.stat-card .stat-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.quick-link {
|
||||
padding: 14px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
.sage-sidebar.mobile-open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,61 +42,9 @@
|
||||
function initSidebar() {
|
||||
var collapsed = false;
|
||||
try { collapsed = localStorage.getItem(SIDEBAR_KEY) === 'true'; } catch(e) {}
|
||||
|
||||
// Auto-collapse on mobile
|
||||
if (isMobile()) {
|
||||
collapsed = true;
|
||||
}
|
||||
|
||||
var sidebar = document.getElementById('sage_sidebar');
|
||||
if (sidebar && collapsed) {
|
||||
sidebar.classList.add('collapsed');
|
||||
sidebar.style.width = '64px';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle window resize - auto-collapse on mobile
|
||||
function handleResize() {
|
||||
var sidebar = document.getElementById('sage_sidebar');
|
||||
if (!sidebar) return;
|
||||
|
||||
if (isMobile() && !sidebar.classList.contains('collapsed')) {
|
||||
sidebar.classList.add('collapsed');
|
||||
sidebar.style.width = '64px';
|
||||
try { localStorage.setItem(SIDEBAR_KEY, 'true'); } catch(e) {}
|
||||
updateSidebarIcon(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're on mobile viewport
|
||||
function isMobile() {
|
||||
return window.innerWidth <= 768;
|
||||
}
|
||||
|
||||
// Close sidebar when clicking outside on mobile
|
||||
function setupMobileOverlay() {
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!isMobile()) return;
|
||||
var sidebar = document.getElementById('sage_sidebar');
|
||||
var toggleBtn = document.getElementById('sidebar_toggle_btn');
|
||||
if (!sidebar || sidebar.classList.contains('collapsed')) return;
|
||||
// If click is outside sidebar and toggle button, close sidebar
|
||||
if (!sidebar.contains(e.target) && (!toggleBtn || !toggleBtn.contains(e.target))) {
|
||||
sidebar.classList.add('collapsed');
|
||||
sidebar.style.width = '64px';
|
||||
try { localStorage.setItem(SIDEBAR_KEY, 'true'); } catch(ex) {}
|
||||
updateSidebarIcon(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateSidebarIcon(isCollapsed) {
|
||||
var btn = document.getElementById('sidebar_toggle_btn');
|
||||
if (!btn) return;
|
||||
if (isCollapsed) {
|
||||
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>';
|
||||
} else {
|
||||
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,152 +55,29 @@
|
||||
sidebar.classList.toggle('collapsed');
|
||||
var isCollapsed = sidebar.classList.contains('collapsed');
|
||||
try { localStorage.setItem(SIDEBAR_KEY, isCollapsed); } catch(e) {}
|
||||
updateSidebarIcon(isCollapsed);
|
||||
// Override inline width style (bricks sets inline style which beats CSS class)
|
||||
sidebar.style.width = isCollapsed ? '64px' : '240px';
|
||||
// Toggle Menu widget collapse state
|
||||
if (typeof bricks !== 'undefined') {
|
||||
var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
|
||||
if (menu && menu.toggle_collapse) {
|
||||
menu.toggle_collapse();
|
||||
|
||||
// Update toggle icon
|
||||
var btn = document.getElementById('sidebar_toggle_btn');
|
||||
if (btn) {
|
||||
if (isCollapsed) {
|
||||
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>';
|
||||
} else {
|
||||
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize SPA Router
|
||||
function initRouter() {
|
||||
if (typeof bricks === 'undefined' || !bricks.Router) {
|
||||
console.log('[Shell] Router not available');
|
||||
return;
|
||||
}
|
||||
bricks.Router.init({
|
||||
targets: [
|
||||
{ id: 'sage_main_content', param: 'page' }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// Called by user_logined event — skip dashboard if Router has a saved route
|
||||
window.sageOnLogin = async function(dashboardUrl) {
|
||||
var target = bricks.getWidgetById('sage_main_content', bricks.app);
|
||||
if (!target) return;
|
||||
|
||||
var hasRoute = false;
|
||||
if (bricks.Router && bricks.Router._enabled) {
|
||||
hasRoute = bricks.Router.current('sage_main_content')
|
||||
|| new URLSearchParams(window.location.search).has('page');
|
||||
}
|
||||
|
||||
if (hasRoute) {
|
||||
console.log('[Shell] Router has route, skip dashboard load');
|
||||
return;
|
||||
}
|
||||
|
||||
// No route — load dashboard via Router
|
||||
if (dashboardUrl && bricks.Router && bricks.Router._enabled) {
|
||||
bricks.Router.navigate('sage_main_content', dashboardUrl);
|
||||
} else if (dashboardUrl) {
|
||||
var desc = { widgettype: 'urlwidget', options: { url: dashboardUrl } };
|
||||
var w = await bricks.widgetBuild(desc, bricks.app);
|
||||
if (w) {
|
||||
target.clear_widgets();
|
||||
target.add_widget(w);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Reload global menu after login/logout
|
||||
window.sageReloadMenu = async function() {
|
||||
if (typeof bricks === 'undefined') return;
|
||||
var sidebar = bricks.getWidgetById('sage_sidebar', bricks.app);
|
||||
if (!sidebar) {
|
||||
console.log('[Shell] sage_sidebar not found');
|
||||
return;
|
||||
}
|
||||
// Check if sidebar is currently collapsed before clearing
|
||||
var isCollapsed = sidebar.el && sidebar.el.classList.contains('collapsed');
|
||||
// Clear existing children
|
||||
sidebar.subwidgets.forEach(function(w) { w.destroy && w.destroy(); });
|
||||
sidebar.subwidgets = [];
|
||||
sidebar.el.innerHTML = '';
|
||||
// Rebuild menu urlwidget
|
||||
var menuUrl = bricks.app.baseUrl + '/global_menu.ui?_webbricks_=1';
|
||||
var desc = {
|
||||
"widgettype": "urlwidget",
|
||||
"options": { "url": menuUrl }
|
||||
};
|
||||
try {
|
||||
var w = await bricks.widgetBuild(desc, sidebar);
|
||||
if (w) {
|
||||
sidebar.addSubWidget(w);
|
||||
// Re-apply collapsed state and inline width to newly built menu
|
||||
if (isCollapsed) {
|
||||
sidebar.el.style.width = '64px';
|
||||
var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
|
||||
if (menu && menu.collapse) {
|
||||
menu.collapse();
|
||||
}
|
||||
}
|
||||
console.log('[Shell] Menu reloaded');
|
||||
}
|
||||
} catch(e) {
|
||||
console.log('[Shell] Menu reload error:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Run on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initTheme();
|
||||
initSidebar();
|
||||
initRouter();
|
||||
setupMobileOverlay();
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
} else {
|
||||
initTheme();
|
||||
initSidebar();
|
||||
initRouter();
|
||||
setupMobileOverlay();
|
||||
window.addEventListener('resize', handleResize);
|
||||
}
|
||||
|
||||
// Bricks widgets render asynchronously after DOMContentLoaded.
|
||||
// Retry updateThemeIcon until the button element exists.
|
||||
(function retryThemeIcon() {
|
||||
var btn = document.getElementById('theme_toggle_btn');
|
||||
if (btn) {
|
||||
var theme = document.documentElement.getAttribute('data-theme') || 'dark';
|
||||
updateThemeIcon(theme);
|
||||
} else {
|
||||
setTimeout(retryThemeIcon, 200);
|
||||
}
|
||||
})();
|
||||
|
||||
// Retry sidebar icon init until the button element exists
|
||||
(function retrySidebarIcon() {
|
||||
var sidebar = document.getElementById('sage_sidebar');
|
||||
var btn = document.getElementById('sidebar_toggle_btn');
|
||||
if (btn && sidebar) {
|
||||
var isCollapsed = sidebar.classList.contains('collapsed');
|
||||
updateSidebarIcon(isCollapsed);
|
||||
// Sync inline width with collapsed state (bricks may have overridden it)
|
||||
sidebar.style.width = isCollapsed ? '64px' : '240px';
|
||||
// Apply collapsed state to Menu if sidebar was already collapsed
|
||||
if (isCollapsed && typeof bricks !== 'undefined') {
|
||||
var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
|
||||
if (menu && menu.collapse) {
|
||||
menu.collapse();
|
||||
} else {
|
||||
setTimeout(retrySidebarIcon, 200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setTimeout(retrySidebarIcon, 200);
|
||||
}
|
||||
})();
|
||||
|
||||
// Expose global functions for bricks bind access
|
||||
window.sageToggleTheme = toggleTheme;
|
||||
window.sageToggleSidebar = toggleSidebar;
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "14px",
|
||||
"borderRadius": "12px",
|
||||
"flex": "1",
|
||||
"minHeight": "90px",
|
||||
"borderLeft": "4px solid #22c55e"
|
||||
},
|
||||
"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": {
|
||||
"css": "stat-value",
|
||||
"text": "{{get_active_users_today(request)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-label",
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "今日活跃用户",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "14px",
|
||||
"bgcolor": "#1E293B",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"border": "1px solid #334155",
|
||||
"flex": "1",
|
||||
"minHeight": "90px",
|
||||
"borderLeft": "4px solid #06b6d4"
|
||||
"minHeight": "110px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
@ -32,21 +32,21 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "{{get_concurrent_users(request)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"color": "#F1F5F9",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "在线用户",
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "在线用户",
|
||||
"i18n": true
|
||||
"color": "#94A3B8",
|
||||
"marginTop": "4px"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,26 +1,13 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "14px",
|
||||
"bgcolor": "#1E293B",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"border": "1px solid #334155",
|
||||
"flex": "1",
|
||||
"minHeight": "90px",
|
||||
"cursor": "pointer",
|
||||
"borderLeft": "4px solid #ef4444"
|
||||
"minHeight": "110px"
|
||||
},
|
||||
"binds": [
|
||||
{
|
||||
"wid": "self",
|
||||
"event": "click",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "app.sage_main_content",
|
||||
"options": {
|
||||
"url": "{{entire_url('/llmage/failed_accounting.ui')}}"
|
||||
},
|
||||
"mode": "replace"
|
||||
}
|
||||
],
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
@ -45,21 +32,21 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "{{get_accounting_errors(request)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"color": "#EF4444",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "记账异常",
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "记账异常",
|
||||
"i18n": true
|
||||
"color": "#94A3B8",
|
||||
"marginTop": "4px"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "14px",
|
||||
"borderRadius": "12px",
|
||||
"flex": "1",
|
||||
"minHeight": "90px",
|
||||
"borderLeft": "4px solid #10b981"
|
||||
},
|
||||
"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": {
|
||||
"css": "stat-value",
|
||||
"text": "{{get_new_users_month(request)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "本月新增用户",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,13 +1,12 @@
|
||||
{% set trend = get_amount_trend(request) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "14px",
|
||||
"bgcolor": "#1E293B",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"border": "1px solid #334155",
|
||||
"flex": "1",
|
||||
"minHeight": "90px",
|
||||
"borderLeft": "4px solid {% if trend.trend == 'up' %}#22c55e{% elif trend.trend == 'down' %}#ef4444{% else %}#8b5cf6{% endif %}"
|
||||
"minHeight": "110px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
@ -20,10 +19,9 @@
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 6v12m6-6H6\"/></svg>",
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22C55E\" stroke-width=\"2\"><path d=\"M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px",
|
||||
"color": "#8b5cf6"
|
||||
"height": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -34,59 +32,21 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "¥{{trend.value|round(2)}}",
|
||||
"text": "¥{{get_today_amount(request)|round(2)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"color": "#F1F5F9",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"alignItems": "center",
|
||||
"text": "今日消费金额",
|
||||
"fontSize": "14px",
|
||||
"color": "#94A3B8",
|
||||
"marginTop": "4px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-label",
|
||||
"fontSize": "14px",
|
||||
"otext": "今日交易金额",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"alignItems": "center",
|
||||
"gap": "4px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "{% if trend.trend == 'up' %}<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22c55e\" stroke-width=\"2\"><polyline points=\"18 15 12 9 6 15\"></polyline></svg>{% elif trend.trend == 'down' %}<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>{% else %}<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#94a3b8\" stroke-width=\"2\"><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line></svg>{% endif %}",
|
||||
"width": "16px",
|
||||
"height": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{trend.percentage|round(1)}}%{% if trend.trend == 'up' %} ↑{% elif trend.trend == 'down' %} ↓{% endif %}",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": "600",
|
||||
"color": "{% if trend.trend == 'up' %}#22c55e{% elif trend.trend == 'down' %}#ef4444{% else %}#94a3b8{% endif %}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
{% set trend = get_usage_trend(request) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "14px",
|
||||
"bgcolor": "#1E293B",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"border": "1px solid #334155",
|
||||
"flex": "1",
|
||||
"minHeight": "90px",
|
||||
"cursor": "pointer",
|
||||
"borderLeft": "4px solid {% if trend.trend == 'up' %}#22c55e{% elif trend.trend == 'down' %}#ef4444{% else %}#3b82f6{% endif %}"
|
||||
"minHeight": "110px",
|
||||
"cursor": "pointer"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
@ -21,10 +20,9 @@
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M7.5 21L3 16.5m0 0L7.5 12M12 9v7.5m0 0l4.5-4.5M12 9l4.5 4.5m0 0L12 16.5\"/><path d=\"M21 12h-4.5M12 3v4.5m0 0L7.5 12\"/></svg>",
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#3B82F6\" stroke-width=\"2\"><path d=\"M7.5 21L3 16.5m0 0L7.5 12M12 9v7.5m0 0l4.5-4.5M12 9l4.5 4.5m0 0L12 16.5\"/><path d=\"M21 12h-4.5M12 3v4.5m0 0L7.5 12\"/></svg>",
|
||||
"width": "24px",
|
||||
"height": "24px",
|
||||
"color": "#3b82f6"
|
||||
"height": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -35,59 +33,21 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "{{trend.value}}",
|
||||
"text": "{{get_today_usage(request)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"color": "#F1F5F9",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"alignItems": "center",
|
||||
"text": "今日调用笔数",
|
||||
"fontSize": "14px",
|
||||
"color": "#94A3B8",
|
||||
"marginTop": "4px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-label",
|
||||
"fontSize": "14px",
|
||||
"otext": "今日调用笔数",
|
||||
"i18n": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Filler"
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"alignItems": "center",
|
||||
"gap": "4px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "{% if trend.trend == 'up' %}<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#22c55e\" stroke-width=\"2\"><polyline points=\"18 15 12 9 6 15\"></polyline></svg>{% elif trend.trend == 'down' %}<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ef4444\" stroke-width=\"2\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>{% else %}<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#94a3b8\" stroke-width=\"2\"><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line></svg>{% endif %}",
|
||||
"width": "16px",
|
||||
"height": "16px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{trend.percentage|round(1)}}%{% if trend.trend == 'up' %} ↑{% elif trend.trend == 'down' %} ↓{% endif %}",
|
||||
"fontSize": "12px",
|
||||
"fontWeight": "600",
|
||||
"color": "{% if trend.trend == 'up' %}#22c55e{% elif trend.trend == 'down' %}#ef4444{% else %}#94a3b8{% endif %}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "14px",
|
||||
"borderRadius": "12px",
|
||||
"flex": "1",
|
||||
"minHeight": "90px",
|
||||
"borderLeft": "4px solid #8b5cf6"
|
||||
},
|
||||
"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": {
|
||||
"css": "stat-value",
|
||||
"text": "{{get_total_orgs(request)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "组织机构数",
|
||||
"i18n": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "stat-card",
|
||||
"padding": "14px",
|
||||
"bgcolor": "#1E293B",
|
||||
"padding": "20px",
|
||||
"borderRadius": "12px",
|
||||
"border": "1px solid #334155",
|
||||
"flex": "1",
|
||||
"minHeight": "90px",
|
||||
"borderLeft": "4px solid #3b82f6"
|
||||
"minHeight": "110px"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
@ -19,10 +19,9 @@
|
||||
{
|
||||
"widgettype": "Svg",
|
||||
"options": {
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" 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>",
|
||||
"svg": "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#A78BFA\" 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>",
|
||||
"width": "24px",
|
||||
"height": "24px",
|
||||
"color": "#3b82f6"
|
||||
"height": "24px"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -33,21 +32,20 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-value",
|
||||
"text": "{{get_total_users(request)}}",
|
||||
"fontSize": "32px",
|
||||
"fontWeight": "700",
|
||||
"color": "#F1F5F9",
|
||||
"lineHeight": "1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"css": "stat-label",
|
||||
"text": "用户总数",
|
||||
"fontSize": "14px",
|
||||
"marginTop": "4px",
|
||||
"otext": "用户总数",
|
||||
"i18n": true
|
||||
"color": "#94A3B8",
|
||||
"marginTop": "4px"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
{% set providers = get_top_providers_by_amount(request) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{% for p in providers %}
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "12px 0",
|
||||
{% if not loop.first %}
|
||||
{% endif %}
|
||||
"alignItems": "center"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{loop.index}}",
|
||||
"width": "30px",
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{p.provider_name}}",
|
||||
"flex": "1",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{p.cnt}} 笔",
|
||||
"width": "80px",
|
||||
"fontSize": "13px",
|
||||
"textAlign": "right"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "¥{{p.total_amount}}",
|
||||
"width": "100px",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "600",
|
||||
"textAlign": "right"
|
||||
}
|
||||
}
|
||||
]
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
{% if not providers %}
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"otext": "暂无数据",
|
||||
|
||||
"i18n": true,
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center",
|
||||
"padding": "20px 0"
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
]
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
{% set providers = get_top_providers_by_count(request) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{% for p in providers %}
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "12px 0",
|
||||
{% if not loop.first %}
|
||||
{% endif %}
|
||||
"alignItems": "center"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{loop.index}}",
|
||||
"width": "30px",
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{p.provider_name}}",
|
||||
"flex": "1",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{p.cnt}} 笔",
|
||||
"width": "80px",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "600",
|
||||
"textAlign": "right"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "¥{{p.total_amount}}",
|
||||
"width": "100px",
|
||||
"fontSize": "13px",
|
||||
"textAlign": "right"
|
||||
}
|
||||
}
|
||||
]
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
{% if not providers %}
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"otext": "暂无数据",
|
||||
|
||||
"i18n": true,
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center",
|
||||
"padding": "20px 0"
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
]
|
||||
}
|
||||
@ -1,74 +1,16 @@
|
||||
{% set users = get_top_users_by_amount(request) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{% for u in users %}
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"widgettype": "RefreshWidget",
|
||||
"id": "table_top_users_amount",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "12px 0",
|
||||
{% if not loop.first %}
|
||||
"borderTop": "1px solid var(--sage-border-weak)",
|
||||
{% endif %}
|
||||
"alignItems": "center"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{loop.index}}",
|
||||
"width": "30px",
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{u.user_name}}",
|
||||
"flex": "1",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{u.cnt}} 笔",
|
||||
"width": "80px",
|
||||
"fontSize": "13px",
|
||||
"textAlign": "right"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "¥{{u.total_amount}}",
|
||||
"width": "100px",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "600",
|
||||
"textAlign": "right"
|
||||
}
|
||||
}
|
||||
]
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
{% if not users %}
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"otext": "暂无数据",
|
||||
|
||||
"i18n": true,
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center",
|
||||
"padding": "20px 0"
|
||||
"period_seconds": 30,
|
||||
"url": "{{entire_url('table_top_users_amount.ui')}}"
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
]
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
"width": "100%",
|
||||
"padding": "12px 0",
|
||||
{% if not loop.first %}
|
||||
"borderTop": "1px solid var(--sage-border-weak)",
|
||||
"borderTop": "1px solid #334155",
|
||||
{% endif %}
|
||||
"alignItems": "center"
|
||||
},
|
||||
@ -22,6 +22,7 @@
|
||||
"options": {
|
||||
"text": "{{loop.index}}",
|
||||
"width": "30px",
|
||||
"color": "#64748B",
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center"
|
||||
}
|
||||
@ -31,6 +32,7 @@
|
||||
"options": {
|
||||
"text": "{{u.user_name}}",
|
||||
"flex": "1",
|
||||
"color": "#F1F5F9",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "500"
|
||||
}
|
||||
@ -40,6 +42,7 @@
|
||||
"options": {
|
||||
"text": "{{u.cnt}} 笔",
|
||||
"width": "80px",
|
||||
"color": "#94A3B8",
|
||||
"fontSize": "13px",
|
||||
"textAlign": "right"
|
||||
}
|
||||
@ -49,6 +52,7 @@
|
||||
"options": {
|
||||
"text": "¥{{u.total_amount}}",
|
||||
"width": "100px",
|
||||
"color": "#22C55E",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "600",
|
||||
"textAlign": "right"
|
||||
@ -61,9 +65,7 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"otext": "暂无数据",
|
||||
|
||||
"i18n": true,
|
||||
"text": "暂无数据",
|
||||
"color": "#64748B",
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center",
|
||||
|
||||
@ -1,73 +0,0 @@
|
||||
{% set users = get_top_users_by_count(request) %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"width": "100%"
|
||||
},
|
||||
"subwidgets": [
|
||||
{% for u in users %}
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"padding": "12px 0",
|
||||
{% if not loop.first %}
|
||||
{% endif %}
|
||||
"alignItems": "center"
|
||||
},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{loop.index}}",
|
||||
"width": "30px",
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{u.user_name}}",
|
||||
"flex": "1",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "500"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "{{u.cnt}} 笔",
|
||||
"width": "80px",
|
||||
"fontSize": "14px",
|
||||
"fontWeight": "600",
|
||||
"textAlign": "right"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "¥{{u.total_amount}}",
|
||||
"width": "100px",
|
||||
"fontSize": "13px",
|
||||
"textAlign": "right"
|
||||
}
|
||||
}
|
||||
]
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
{% if not users %}
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"otext": "暂无数据",
|
||||
|
||||
"i18n": true,
|
||||
"fontSize": "14px",
|
||||
"textAlign": "center",
|
||||
"padding": "20px 0"
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
]
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"bgcolor": "var(--sage-bg-card, #FFFFFF)",
|
||||
"bgcolor": "#FFFFFF",
|
||||
"padding": "24px",
|
||||
"borderRadius": "8px",
|
||||
"flex": "1",
|
||||
@ -12,11 +12,10 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "今日交易金额",
|
||||
"fontSize": "14px",
|
||||
"color": "#888",
|
||||
"marginBottom": "8px",
|
||||
"otext": "今日交易金额",
|
||||
"i18n": true
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -30,4 +29,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,11 +12,10 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "今日调用笔数",
|
||||
"fontSize": "14px",
|
||||
"color": "#888",
|
||||
"marginBottom": "8px",
|
||||
"otext": "今日调用笔数",
|
||||
"i18n": true
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -30,4 +29,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,9 +10,7 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"otext": "用户金额 TOP 5(今日)",
|
||||
|
||||
"i18n": true,
|
||||
"text": "用户金额 TOP 5(今日)",
|
||||
"fontSize": "16px",
|
||||
"fontWeight": "bold",
|
||||
"color": "#333",
|
||||
@ -29,14 +27,10 @@
|
||||
"widgettype": "HBox",
|
||||
"options": {"bgcolor": "#f5f5f5", "padding": "8px 12px", "borderRadius": "4px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Text", "options": {"otext": "排名",
|
||||
"i18n": true, "fontSize": "12px", "color": "#888", "width": "50px"}},
|
||||
{"widgettype": "Text", "options": {"otext": "用户",
|
||||
"i18n": true, "fontSize": "12px", "color": "#888", "flex": "1"}},
|
||||
{"widgettype": "Text", "options": {"otext": "金额",
|
||||
"i18n": true, "fontSize": "12px", "color": "#888", "width": "100px", "textAlign": "right"}},
|
||||
{"widgettype": "Text", "options": {"otext": "笔数",
|
||||
"i18n": true, "fontSize": "12px", "color": "#888", "width": "60px", "textAlign": "right"}}
|
||||
{"widgettype": "Text", "options": {"text": "排名", "fontSize": "12px", "color": "#888", "width": "50px"}},
|
||||
{"widgettype": "Text", "options": {"text": "用户", "fontSize": "12px", "color": "#888", "flex": "1"}},
|
||||
{"widgettype": "Text", "options": {"text": "金额", "fontSize": "12px", "color": "#888", "width": "100px", "textAlign": "right"}},
|
||||
{"widgettype": "Text", "options": {"text": "笔数", "fontSize": "12px", "color": "#888", "width": "60px", "textAlign": "right"}}
|
||||
]
|
||||
},
|
||||
{% for item in rows %}
|
||||
@ -56,8 +50,7 @@
|
||||
{% else %}
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {"otext": "暂无数据",
|
||||
"i18n": true, "fontSize": "14px", "color": "#999", "textAlign": "center"}
|
||||
"options": {"text": "暂无数据", "fontSize": "14px", "color": "#999", "textAlign": "center"}
|
||||
}
|
||||
{% endif %}
|
||||
]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"bgcolor": "var(--sage-bg-card, #FFFFFF)",
|
||||
"bgcolor": "#FFFFFF",
|
||||
"padding": "24px",
|
||||
"borderRadius": "8px",
|
||||
"flex": "1",
|
||||
@ -12,11 +12,10 @@
|
||||
{
|
||||
"widgettype": "Text",
|
||||
"options": {
|
||||
"text": "用户总数",
|
||||
"fontSize": "14px",
|
||||
"color": "#888",
|
||||
"marginBottom": "8px",
|
||||
"otext": "用户总数",
|
||||
"i18n": true
|
||||
"marginBottom": "8px"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -30,4 +29,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"widgettype": "ChartBar",
|
||||
"options": {
|
||||
"height": "280px",
|
||||
"width": "100%",
|
||||
"data_url": "{{entire_url('api/user_today_models.dspy')}}",
|
||||
"nameField": "model_name",
|
||||
"valueFields": ["cnt", "total_amount"],
|
||||
"refresh_period": 30
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user