feat: 实时数据Dashboard+客户/分销商角色视图
- dashboards.py: 新增实时指标(直查llmusage,无ETL延迟) + 分销商专用API + scope过滤版 - init.py: 角色检测(_resolve_view),实时卡片J2函数,分销商API端点 - index.ui: 7个实时卡片(15s刷新)替换旧stat_*卡片 - 新增rt_card_*.ui(7个)+api端点(2个) - load_path.py: 注册新UI路径
This commit is contained in:
parent
43a115a14d
commit
05584d8871
@ -1,12 +1,137 @@
|
||||
"""
|
||||
统一 Dashboard API — 从集市表查询所有运营指标 + 性能指标
|
||||
替代 dashboard_for_sage 中对 llmusage 的直接查询
|
||||
统一 Dashboard API — 从集市表 / llmusage 查询运营指标 + 性能指标
|
||||
支持客户(customer)和分销商(distributor)两种视图
|
||||
实时指标直接查 llmusage,非实时从集市表取
|
||||
"""
|
||||
from sqlor.dbpools import get_sor_context
|
||||
from datetime import datetime, timedelta
|
||||
import redis.asyncio as redis
|
||||
|
||||
# ── 角色/范围辅助 ──
|
||||
|
||||
CUSTOMER = 'customer'
|
||||
DISTRIBUTOR = 'distributor'
|
||||
|
||||
|
||||
# ── 统计卡片 ──
|
||||
def _fact_where(scope, org_filter):
|
||||
"""根据角色返回 dm_model_call_fact 的 WHERE 条件"""
|
||||
if scope == DISTRIBUTOR and org_filter:
|
||||
return "distributor_orgid = ${org}$"
|
||||
elif scope == CUSTOMER and org_filter:
|
||||
return "userorgid = ${org}$"
|
||||
return "1=1"
|
||||
|
||||
|
||||
def _usage_where(scope, org_filter):
|
||||
"""根据角色返回 llmusage 的 WHERE 条件"""
|
||||
if scope == DISTRIBUTOR and org_filter:
|
||||
return "distributor_orgid = ${org}$"
|
||||
elif scope == CUSTOMER and org_filter:
|
||||
return "userorgid = ${org}$"
|
||||
return "1=1"
|
||||
|
||||
|
||||
# ── 实时指标卡片(直接查 llmusage,无 ETL 延迟)──
|
||||
|
||||
async def get_realtime_calls(sor, today, scope=None, org_filter=None):
|
||||
"""实时今日调用笔数"""
|
||||
w = _usage_where(scope, org_filter)
|
||||
sql = ("SELECT COUNT(*) as cnt FROM llmusage "
|
||||
"WHERE use_date = ${today}$ AND " + w)
|
||||
ns = {'today': today}
|
||||
if org_filter:
|
||||
ns['org'] = org_filter
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
return int(recs[0].cnt) if recs else 0
|
||||
|
||||
|
||||
async def get_realtime_amount(sor, today, scope=None, org_filter=None):
|
||||
"""实时今日交易金额(原币汇总)"""
|
||||
w = _usage_where(scope, org_filter)
|
||||
sql = ("SELECT COALESCE(SUM(amount), 0) as total FROM llmusage "
|
||||
"WHERE use_date = ${today}$ AND " + w)
|
||||
ns = {'today': today}
|
||||
if org_filter:
|
||||
ns['org'] = org_filter
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
return round(float(recs[0].total), 2) if recs else 0
|
||||
|
||||
|
||||
async def get_total_users_count(sor, scope=None, org_filter=None):
|
||||
"""用户总数"""
|
||||
if scope == CUSTOMER and org_filter:
|
||||
sql = "SELECT COUNT(*) as cnt FROM users WHERE userorgid = ${org}$"
|
||||
ns = {'org': org_filter}
|
||||
elif scope == DISTRIBUTOR and org_filter:
|
||||
# 分销商下所有客户机构的用户
|
||||
sql = ("SELECT COUNT(DISTINCT u.id) as cnt FROM users u "
|
||||
"JOIN organization o ON o.id = u.userorgid "
|
||||
"WHERE o.resellerid = ${org}$")
|
||||
ns = {'org': org_filter}
|
||||
else:
|
||||
sql = "SELECT COUNT(*) as cnt FROM users"
|
||||
ns = {}
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
return int(recs[0].cnt) if recs else 0
|
||||
|
||||
|
||||
async def get_realtime_active_users(sor, today, scope=None, org_filter=None):
|
||||
"""实时今日活跃用户数"""
|
||||
w = _usage_where(scope, org_filter)
|
||||
sql = ("SELECT COUNT(DISTINCT userid) as cnt FROM llmusage "
|
||||
"WHERE use_date = ${today}$ AND " + w)
|
||||
ns = {'today': today}
|
||||
if org_filter:
|
||||
ns['org'] = org_filter
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
return int(recs[0].cnt) if recs else 0
|
||||
|
||||
|
||||
async def get_online_users(env, scope=None, org_filter=None):
|
||||
"""在线用户数 — 从 Redis session 中统计"""
|
||||
try:
|
||||
config = env.get_config()
|
||||
redis_url = getattr(config, 'redis_url', 'redis://127.0.0.1:6379')
|
||||
r = redis.from_url(redis_url, decode_responses=True)
|
||||
count = 0
|
||||
cursor = 0
|
||||
while True:
|
||||
cursor, keys = await r.scan(cursor, match='AIOHTTP_SESSION_*', count=100)
|
||||
for k in keys:
|
||||
data = await r.get(k)
|
||||
if data:
|
||||
count += 1
|
||||
if cursor == 0:
|
||||
break
|
||||
await r.close()
|
||||
return count
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
async def get_online_users_by_org(env, org_filter, scope=None):
|
||||
"""按机构统计在线用户数"""
|
||||
try:
|
||||
config = env.get_config()
|
||||
redis_url = getattr(config, 'redis_url', 'redis://127.0.0.1:6379')
|
||||
r = redis.from_url(redis_url, decode_responses=True)
|
||||
count = 0
|
||||
cursor = 0
|
||||
while True:
|
||||
cursor, keys = await r.scan(cursor, match='AIOHTTP_SESSION_*', count=100)
|
||||
for k in keys:
|
||||
data = await r.get(k)
|
||||
if data and org_filter in data:
|
||||
count += 1
|
||||
if cursor == 0:
|
||||
break
|
||||
await r.close()
|
||||
return count
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
# ── 统计卡片(集市表,ETL 更新)──
|
||||
|
||||
async def get_today_usage(env, sor):
|
||||
"""当天调用总数"""
|
||||
@ -163,3 +288,76 @@ async def get_hourly_concurrency(env, sor):
|
||||
GROUP BY call_hour ORDER BY call_hour"""
|
||||
recs = await sor.sqlExe(sql, {'today': env.curDateString()})
|
||||
return [{'hour': r.call_hour, 'users': int(r.users), 'calls': int(r.calls)} for r in recs]
|
||||
|
||||
|
||||
# ── 分销商专用 ──
|
||||
|
||||
async def get_distributor_customers_count(sor, distributor_orgid):
|
||||
"""分销商旗下客户数"""
|
||||
sql = """SELECT COUNT(DISTINCT userorgid) as cnt FROM dm_model_call_fact
|
||||
WHERE distributor_orgid = ${org}$"""
|
||||
recs = await sor.sqlExe(sql, {'org': distributor_orgid})
|
||||
return int(recs[0].cnt) if recs else 0
|
||||
|
||||
|
||||
async def get_distributor_active_customers(sor, today, distributor_orgid):
|
||||
"""分销商今日活跃客户数"""
|
||||
sql = """SELECT COUNT(DISTINCT userorgid) as cnt FROM dm_model_call_fact
|
||||
WHERE call_date = ${today}$ AND distributor_orgid = ${org}$"""
|
||||
recs = await sor.sqlExe(sql, {'today': today, 'org': distributor_orgid})
|
||||
return int(recs[0].cnt) if recs else 0
|
||||
|
||||
|
||||
async def get_distributor_top_customers(sor, distributor_orgid, limit=5):
|
||||
"""分销商旗下客户排行(按金额)"""
|
||||
sql = """SELECT userorgid, COUNT(*) as cnt, COALESCE(SUM(amount_cny), 0) as total_amount
|
||||
FROM dm_model_call_fact WHERE distributor_orgid = ${org}$
|
||||
GROUP BY userorgid ORDER BY total_amount DESC LIMIT ${limit}$"""
|
||||
recs = await sor.sqlExe(sql, {'org': distributor_orgid, 'limit': str(limit)})
|
||||
return [{'org_id': r.userorgid, 'cnt': int(r.cnt),
|
||||
'total_amount': round(float(r.total_amount), 2)} for r in recs]
|
||||
|
||||
|
||||
# ── scope 过滤版(在全局函数基础上加 org_filter)──
|
||||
|
||||
async def get_scoped_today_usage(sor, today, scope=None, org_filter=None):
|
||||
w = _fact_where(scope, org_filter)
|
||||
sql = "SELECT COUNT(*) as cnt FROM dm_model_call_fact WHERE call_date = ${today}$ AND " + w
|
||||
ns = {'today': today}
|
||||
if org_filter:
|
||||
ns['org'] = org_filter
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
return int(recs[0].cnt) if recs else 0
|
||||
|
||||
|
||||
async def get_scoped_today_amount(sor, today, scope=None, org_filter=None):
|
||||
w = _fact_where(scope, org_filter)
|
||||
sql = "SELECT COALESCE(SUM(amount_cny), 0) as total FROM dm_model_call_fact WHERE call_date = ${today}$ AND " + w
|
||||
ns = {'today': today}
|
||||
if org_filter:
|
||||
ns['org'] = org_filter
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
return round(float(recs[0].total), 2) if recs else 0
|
||||
|
||||
|
||||
async def get_scoped_success_rate(sor, today, scope=None, org_filter=None):
|
||||
w = _fact_where(scope, org_filter)
|
||||
sql = ("SELECT COUNT(*) as total, SUM(CASE WHEN status='SUCCEEDED' THEN 1 ELSE 0 END) as success "
|
||||
"FROM dm_model_call_fact WHERE call_date = ${today}$ AND " + w)
|
||||
ns = {'today': today}
|
||||
if org_filter:
|
||||
ns['org'] = org_filter
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
if recs and recs[0].total:
|
||||
return round(recs[0].success * 100.0 / recs[0].total, 1)
|
||||
return 0
|
||||
|
||||
|
||||
async def get_scoped_fail_count(sor, today, scope=None, org_filter=None):
|
||||
w = _fact_where(scope, org_filter)
|
||||
sql = "SELECT COUNT(*) as cnt FROM dm_model_call_fact WHERE call_date = ${today}$ AND status = 'FAILED' AND " + w
|
||||
ns = {'today': today}
|
||||
if org_filter:
|
||||
ns['org'] = org_filter
|
||||
recs = await sor.sqlExe(sql, ns)
|
||||
return int(recs[0].cnt) if recs else 0
|
||||
|
||||
@ -1,20 +1,50 @@
|
||||
"""sage_datamart 模块初始化 — 统一 Dashboard API"""
|
||||
"""sage_datamart 模块初始化 — 统一 Dashboard API,支持客户/分销商角色"""
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from appPublic.log import debug
|
||||
from sqlor.dbpools import get_sor_context
|
||||
from .etl import sync_call_fact, aggregate_daily_perf, aggregate_provider_cost
|
||||
|
||||
from .dashboards import (
|
||||
CUSTOMER, DISTRIBUTOR,
|
||||
get_today_usage, get_today_amount, get_success_rate, get_fail_count,
|
||||
get_active_users, get_top_models, get_top_providers, get_top_users,
|
||||
get_customer_daily_models, get_customer_monthly_models,
|
||||
get_customer_user_today, get_daily_trend, get_hourly_concurrency,
|
||||
get_avg_ttft, get_currency_breakdown,
|
||||
get_realtime_calls, get_realtime_amount, get_total_users_count,
|
||||
get_realtime_active_users, get_online_users,
|
||||
get_distributor_customers_count, get_distributor_active_customers,
|
||||
get_distributor_top_customers,
|
||||
get_scoped_today_usage, get_scoped_today_amount,
|
||||
get_scoped_success_rate, get_scoped_fail_count,
|
||||
)
|
||||
|
||||
MODULE_NAME = "sage_datamart"
|
||||
|
||||
|
||||
async def _resolve_view(request):
|
||||
"""解析当前用户角色和机构过滤条件
|
||||
返回 (scope, org_filter) — scope: 'customer'|'distributor', org_filter: org_id
|
||||
"""
|
||||
env = request._run_ns
|
||||
userorgid = await env.get_userorgid()
|
||||
scope = CUSTOMER
|
||||
org_filter = userorgid
|
||||
try:
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT org_type FROM organization WHERE id = ${id}$",
|
||||
{'id': userorgid})
|
||||
if recs:
|
||||
org_type = (recs[0].org_type if hasattr(recs[0], 'org_type')
|
||||
else recs[0].get('org_type', ''))
|
||||
if org_type == 'reseller':
|
||||
scope = DISTRIBUTOR
|
||||
except Exception:
|
||||
pass
|
||||
return scope, org_filter
|
||||
|
||||
|
||||
# ── 性能指标 API ──
|
||||
|
||||
async def api_model_perf(sor, params_kw=None):
|
||||
@ -49,10 +79,9 @@ async def api_provider_roi(sor, params_kw=None):
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
# ── 运营指标 API(替代 dashboard_for_sage)──
|
||||
# ── 运营指标 API ──
|
||||
|
||||
async def api_stats(request):
|
||||
"""统计卡片数据 — /sage_datamart/api/stats.dspy"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return {
|
||||
@ -65,28 +94,24 @@ async def api_stats(request):
|
||||
|
||||
|
||||
async def api_top_models(request):
|
||||
"""Top 模型 — /sage_datamart/api/top_models.dspy"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_top_models(env, sor)
|
||||
|
||||
|
||||
async def api_top_providers(request):
|
||||
"""Top 供应商 — /sage_datamart/api/top_providers.dspy"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_top_providers(env, sor)
|
||||
|
||||
|
||||
async def api_top_users(request):
|
||||
"""Top 用户 — /sage_datamart/api/top_users.dspy"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_top_users(env, sor)
|
||||
|
||||
|
||||
async def api_customer_models(request):
|
||||
"""客户用量 — /sage_datamart/api/customer_models.dspy"""
|
||||
env = request._run_ns
|
||||
userorgid = await env.get_userorgid()
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
@ -97,27 +122,124 @@ async def api_customer_models(request):
|
||||
|
||||
|
||||
async def api_daily_trend(request):
|
||||
"""7天趋势 — /sage_datamart/api/daily_trend.dspy"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_daily_trend(env, sor)
|
||||
|
||||
|
||||
async def api_hourly_concurrency(request):
|
||||
"""每小时并发 — /sage_datamart/api/hourly_concurrency.dspy"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_hourly_concurrency(env, sor)
|
||||
|
||||
|
||||
async def api_currency_stats(request):
|
||||
"""币种分布统计 — /sage_datamart/api/currency_stats.dspy"""
|
||||
env = request._run_ns
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_currency_breakdown(env, sor)
|
||||
|
||||
|
||||
# ── Jinja2 模板函数(给 stat_*.ui 调用)──
|
||||
# ── 实时指标 API(角色感知)──
|
||||
|
||||
async def api_realtime_cards(request):
|
||||
"""实时统计卡片 — /sage_datamart/api/realtime_cards.dspy"""
|
||||
env = request._run_ns
|
||||
scope, org_filter = await _resolve_view(request)
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
if scope == DISTRIBUTOR:
|
||||
return {
|
||||
'scope': scope,
|
||||
'calls': await get_realtime_calls(sor, today, scope, org_filter),
|
||||
'amount': await get_realtime_amount(sor, today, scope, org_filter),
|
||||
'total_users': await get_distributor_customers_count(sor, org_filter),
|
||||
'active_customers': await get_distributor_active_customers(sor, today, org_filter),
|
||||
'online_users': await get_online_users(env),
|
||||
'success_rate': await get_scoped_success_rate(sor, today, scope, org_filter),
|
||||
'fail_count': await get_scoped_fail_count(sor, today, scope, org_filter),
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'scope': scope,
|
||||
'calls': await get_realtime_calls(sor, today, scope, org_filter),
|
||||
'amount': await get_realtime_amount(sor, today, scope, org_filter),
|
||||
'total_users': await get_total_users_count(sor, scope, org_filter),
|
||||
'active_users': await get_realtime_active_users(sor, today, scope, org_filter),
|
||||
'online_users': await get_online_users(env),
|
||||
'success_rate': await get_scoped_success_rate(sor, today, scope, org_filter),
|
||||
'fail_count': await get_scoped_fail_count(sor, today, scope, org_filter),
|
||||
}
|
||||
|
||||
|
||||
# ── 分销商专用 API ──
|
||||
|
||||
async def api_distributor_customers(request):
|
||||
"""分销商客户排行 — /sage_datamart/api/distributor_customers.dspy"""
|
||||
env = request._run_ns
|
||||
scope, org_filter = await _resolve_view(request)
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_distributor_top_customers(sor, org_filter)
|
||||
|
||||
|
||||
# ── Jinja2 模板函数(实时,角色感知)──
|
||||
|
||||
async def j2_realtime_calls(request):
|
||||
env = request._run_ns
|
||||
scope, org_filter = await _resolve_view(request)
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_realtime_calls(sor, today, scope, org_filter)
|
||||
|
||||
|
||||
async def j2_realtime_amount(request):
|
||||
env = request._run_ns
|
||||
scope, org_filter = await _resolve_view(request)
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_realtime_amount(sor, today, scope, org_filter)
|
||||
|
||||
|
||||
async def j2_total_users(request):
|
||||
env = request._run_ns
|
||||
scope, org_filter = await _resolve_view(request)
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
if scope == DISTRIBUTOR:
|
||||
return await get_distributor_customers_count(sor, org_filter)
|
||||
return await get_total_users_count(sor, scope, org_filter)
|
||||
|
||||
|
||||
async def j2_active_today(request):
|
||||
env = request._run_ns
|
||||
scope, org_filter = await _resolve_view(request)
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
if scope == DISTRIBUTOR:
|
||||
return await get_distributor_active_customers(sor, today, org_filter)
|
||||
return await get_realtime_active_users(sor, today, scope, org_filter)
|
||||
|
||||
|
||||
async def j2_online_users(request):
|
||||
env = request._run_ns
|
||||
return await get_online_users(env)
|
||||
|
||||
|
||||
async def j2_scoped_success_rate(request):
|
||||
env = request._run_ns
|
||||
scope, org_filter = await _resolve_view(request)
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_scoped_success_rate(sor, today, scope, org_filter)
|
||||
|
||||
|
||||
async def j2_scoped_fail_count(request):
|
||||
env = request._run_ns
|
||||
scope, org_filter = await _resolve_view(request)
|
||||
today = env.curDateString()
|
||||
async with get_sor_context(env, MODULE_NAME) as sor:
|
||||
return await get_scoped_fail_count(sor, today, scope, org_filter)
|
||||
|
||||
|
||||
# ── 向后兼容 Jinja2 函数 ──
|
||||
|
||||
async def j2_today_usage(request):
|
||||
env = request._run_ns
|
||||
@ -155,10 +277,9 @@ async def j2_avg_ttft(request):
|
||||
return await get_avg_ttft(env, sor)
|
||||
|
||||
|
||||
# ── Cron ETL 函数 ──
|
||||
# ── Cron ETL ──
|
||||
|
||||
async def cron_etl_sync(request):
|
||||
"""ETL: 增量同步 llmusage → dm_model_call_fact"""
|
||||
ip = request['client_ip']
|
||||
if ip not in ['127.0.0.1']:
|
||||
return {'error': f'IP {ip} not allowed'}
|
||||
@ -169,7 +290,6 @@ async def cron_etl_sync(request):
|
||||
|
||||
|
||||
async def cron_etl_aggregate(request):
|
||||
"""ETL: 聚合 dm_model_call_fact → dm_model_perf_daily"""
|
||||
ip = request['client_ip']
|
||||
if ip not in ['127.0.0.1']:
|
||||
return {'error': f'IP {ip} not allowed'}
|
||||
@ -180,7 +300,6 @@ async def cron_etl_aggregate(request):
|
||||
|
||||
|
||||
async def cron_etl_provider_cost(request):
|
||||
"""ETL: 聚合供应商性价比 dm_provider_cost_daily"""
|
||||
ip = request['client_ip']
|
||||
if ip not in ['127.0.0.1']:
|
||||
return {'error': f'IP {ip} not allowed'}
|
||||
@ -190,9 +309,6 @@ async def cron_etl_provider_cost(request):
|
||||
return {'status': 'ok', 'aggregated': count}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def load_sage_datamart():
|
||||
env = ServerEnv()
|
||||
env.api_model_perf = api_model_perf
|
||||
@ -205,14 +321,23 @@ def load_sage_datamart():
|
||||
env.api_daily_trend = api_daily_trend
|
||||
env.api_hourly_concurrency = api_hourly_concurrency
|
||||
env.api_currency_stats = api_currency_stats
|
||||
env.api_realtime_cards = api_realtime_cards
|
||||
env.api_distributor_customers = api_distributor_customers
|
||||
env.j2_today_usage = j2_today_usage
|
||||
env.j2_today_amount = j2_today_amount
|
||||
env.j2_active_users = j2_active_users
|
||||
env.j2_fail_count = j2_fail_count
|
||||
env.j2_success_rate = j2_success_rate
|
||||
env.j2_avg_ttft = j2_avg_ttft
|
||||
env.j2_realtime_calls = j2_realtime_calls
|
||||
env.j2_realtime_amount = j2_realtime_amount
|
||||
env.j2_total_users = j2_total_users
|
||||
env.j2_active_today = j2_active_today
|
||||
env.j2_online_users = j2_online_users
|
||||
env.j2_scoped_success_rate = j2_scoped_success_rate
|
||||
env.j2_scoped_fail_count = j2_scoped_fail_count
|
||||
env.cron_etl_sync = cron_etl_sync
|
||||
env.cron_etl_aggregate = cron_etl_aggregate
|
||||
env.cron_etl_provider_cost = cron_etl_provider_cost
|
||||
|
||||
debug("[sage_datamart] registered all API endpoints and cron functions")
|
||||
debug("[sage_datamart] registered all API + realtime + role-aware endpoints")
|
||||
|
||||
@ -28,6 +28,13 @@ PATHS_LOGINED = [
|
||||
f"/{MOD}/stat_fail_calls.ui",
|
||||
f"/{MOD}/stat_today_usage.ui",
|
||||
f"/{MOD}/stat_today_amount.ui",
|
||||
f"/{MOD}/rt_card_calls.ui",
|
||||
f"/{MOD}/rt_card_amount.ui",
|
||||
f"/{MOD}/rt_card_users.ui",
|
||||
f"/{MOD}/rt_card_active.ui",
|
||||
f"/{MOD}/rt_card_online.ui",
|
||||
f"/{MOD}/rt_card_success.ui",
|
||||
f"/{MOD}/rt_card_fail.ui",
|
||||
]
|
||||
|
||||
def run(role, path):
|
||||
|
||||
1
wwwroot/api/distributor_customers.dspy
Normal file
1
wwwroot/api/distributor_customers.dspy
Normal file
@ -0,0 +1 @@
|
||||
return await api_distributor_customers(request)
|
||||
1
wwwroot/api/realtime_cards.dspy
Normal file
1
wwwroot/api/realtime_cards.dspy
Normal file
@ -0,0 +1 @@
|
||||
return await api_realtime_cards(request)
|
||||
@ -12,20 +12,21 @@
|
||||
"widgettype": "HBox",
|
||||
"options": {"width": "100%", "alignItems": "center", "marginBottom": "12px"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "Title2", "options": {"fontWeight": "700", "otext": "数据概览", "i18n": true}},
|
||||
{"widgettype": "Title2", "options": {"fontWeight": "700", "otext": "实时数据概览", "i18n": true}},
|
||||
{"widgettype": "Filler"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "HBox",
|
||||
"options": {"width": "100%", "gap": "16px", "marginBottom": "16px"},
|
||||
"options": {"width": "100%", "gap": "12px", "marginBottom": "16px", "flexWrap": "wrap"},
|
||||
"subwidgets": [
|
||||
{"widgettype": "RefreshWidget", "id": "stat_today_usage", "options": {"period_seconds": 30, "url": "{{entire_url('stat_today_usage.ui')}}", "width": "16%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "stat_today_amount", "options": {"period_seconds": 30, "url": "{{entire_url('stat_today_amount.ui')}}", "width": "16%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "stat_success_rate", "options": {"period_seconds": 60, "url": "{{entire_url('stat_success_rate.ui')}}", "width": "16%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "stat_fail_calls", "options": {"period_seconds": 60, "url": "{{entire_url('stat_fail_calls.ui')}}", "width": "16%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "stat_avg_ttft", "options": {"period_seconds": 60, "url": "{{entire_url('stat_avg_ttft.ui')}}", "width": "16%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "stat_total_users", "options": {"period_seconds": 60, "url": "{{entire_url('stat_total_users.ui')}}", "width": "16%"}}
|
||||
{"widgettype": "RefreshWidget", "id": "rt_calls", "options": {"period_seconds": 15, "url": "{{entire_url('rt_card_calls.ui')}}", "width": "13%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "rt_amount", "options": {"period_seconds": 15, "url": "{{entire_url('rt_card_amount.ui')}}", "width": "13%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "rt_users", "options": {"period_seconds": 30, "url": "{{entire_url('rt_card_users.ui')}}", "width": "13%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "rt_active", "options": {"period_seconds": 30, "url": "{{entire_url('rt_card_active.ui')}}", "width": "13%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "rt_online", "options": {"period_seconds": 30, "url": "{{entire_url('rt_card_online.ui')}}", "width": "13%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "rt_success", "options": {"period_seconds": 30, "url": "{{entire_url('rt_card_success.ui')}}", "width": "13%"}},
|
||||
{"widgettype": "RefreshWidget", "id": "rt_fail", "options": {"period_seconds": 30, "url": "{{entire_url('rt_card_fail.ui')}}", "width": "13%"}}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,21 +1,28 @@
|
||||
{% set roles = get_user_roles(get_user()) %}
|
||||
{% set userorgid = get_user_orgid() %}
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {"width": "100%"},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "MenuItem",
|
||||
"options": {
|
||||
"label": "实时数据概览",
|
||||
"url": "{{entire_url('/sage_datamart/index.ui')}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "MenuItem",
|
||||
"options": {
|
||||
"label": "模型性能",
|
||||
"url": "{{entire_url('/sage_datamart/index.ui')}}",
|
||||
"icon": "{{entire_url('/sage_datamart/imgs/perf.svg')}}"
|
||||
"url": "{{entire_url('/sage_datamart/index.ui')}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype": "MenuItem",
|
||||
"options": {
|
||||
"label": "供应商性价比",
|
||||
"url": "{{entire_url('/sage_datamart/provider_roi.ui')}}",
|
||||
"icon": "{{entire_url('/sage_datamart/imgs/roi.svg')}}"
|
||||
"url": "{{entire_url('/sage_datamart/provider_roi.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
2
wwwroot/rt_card_active.ui
Normal file
2
wwwroot/rt_card_active.ui
Normal file
@ -0,0 +1,2 @@
|
||||
{% set cnt = j2_active_today(request) %}
|
||||
{"widgettype":"VBox","options":{"css":"card","padding":"16px","borderRadius":"12px","width":"100%"},"subwidgets":[{"widgettype":"Text","options":{"text":"今日活跃","fontSize":"12px","color":"var(--sage-text-secondary)"}},{"widgettype":"Title2","options":{"text":"{{cnt}}","fontWeight":"700","marginTop":"4px"}}]}
|
||||
2
wwwroot/rt_card_amount.ui
Normal file
2
wwwroot/rt_card_amount.ui
Normal file
@ -0,0 +1,2 @@
|
||||
{% set amt = j2_realtime_amount(request) %}
|
||||
{"widgettype":"VBox","options":{"css":"card","padding":"16px","borderRadius":"12px","width":"100%"},"subwidgets":[{"widgettype":"Text","options":{"text":"今日交易金额","fontSize":"12px","color":"var(--sage-text-secondary)"}},{"widgettype":"Title2","options":{"text":"¥{{'%.2f' % amt}}","fontWeight":"700","marginTop":"4px"}}]}
|
||||
2
wwwroot/rt_card_calls.ui
Normal file
2
wwwroot/rt_card_calls.ui
Normal file
@ -0,0 +1,2 @@
|
||||
{% set cnt = j2_realtime_calls(request) %}
|
||||
{"widgettype":"VBox","options":{"css":"card","padding":"16px","borderRadius":"12px","width":"100%"},"subwidgets":[{"widgettype":"Text","options":{"text":"今日调用笔数","fontSize":"12px","color":"var(--sage-text-secondary)"}},{"widgettype":"Title2","options":{"text":"{{cnt}}","fontWeight":"700","marginTop":"4px"}}]}
|
||||
2
wwwroot/rt_card_fail.ui
Normal file
2
wwwroot/rt_card_fail.ui
Normal file
@ -0,0 +1,2 @@
|
||||
{% set cnt = j2_scoped_fail_count(request) %}
|
||||
{"widgettype":"VBox","options":{"css":"card","padding":"16px","borderRadius":"12px","width":"100%"},"subwidgets":[{"widgettype":"Text","options":{"text":"失败数","fontSize":"12px","color":"var(--sage-text-secondary)"}},{"widgettype":"Title2","options":{"text":"{{cnt}}","fontWeight":"700","marginTop":"4px"}}]}
|
||||
2
wwwroot/rt_card_online.ui
Normal file
2
wwwroot/rt_card_online.ui
Normal file
@ -0,0 +1,2 @@
|
||||
{% set cnt = j2_online_users(request) %}
|
||||
{"widgettype":"VBox","options":{"css":"card","padding":"16px","borderRadius":"12px","width":"100%"},"subwidgets":[{"widgettype":"Text","options":{"text":"在线用户","fontSize":"12px","color":"var(--sage-text-secondary)"}},{"widgettype":"Title2","options":{"text":"{{cnt}}","fontWeight":"700","marginTop":"4px"}}]}
|
||||
2
wwwroot/rt_card_success.ui
Normal file
2
wwwroot/rt_card_success.ui
Normal file
@ -0,0 +1,2 @@
|
||||
{% set rate = j2_scoped_success_rate(request) %}
|
||||
{"widgettype":"VBox","options":{"css":"card","padding":"16px","borderRadius":"12px","width":"100%"},"subwidgets":[{"widgettype":"Text","options":{"text":"成功率","fontSize":"12px","color":"var(--sage-text-secondary)"}},{"widgettype":"Title2","options":{"text":"{{rate}}%","fontWeight":"700","marginTop":"4px"}}]}
|
||||
2
wwwroot/rt_card_users.ui
Normal file
2
wwwroot/rt_card_users.ui
Normal file
@ -0,0 +1,2 @@
|
||||
{% set cnt = j2_total_users(request) %}
|
||||
{"widgettype":"VBox","options":{"css":"card","padding":"16px","borderRadius":"12px","width":"100%"},"subwidgets":[{"widgettype":"Text","options":{"text":"用户总数","fontSize":"12px","color":"var(--sage-text-secondary)"}},{"widgettype":"Title2","options":{"text":"{{cnt}}","fontWeight":"700","marginTop":"4px"}}]}
|
||||
Loading…
x
Reference in New Issue
Block a user