Fix admin dashboard summary nulls

This commit is contained in:
john
2026-06-20 17:04:30 +08:00
parent dae3e2f350
commit a59823d347
2 changed files with 52 additions and 26 deletions
+19 -2
View File
@@ -229,9 +229,26 @@ export function createLocalUserAuth(pool) {
async function getAdminSummary() {
const [[row]] = await pool.execute(
`SELECT COUNT(*) AS total, SUM(CASE WHEN balance_cents < 0 THEN 1 ELSE 0 END) AS lowBalance FROM auth_users`,
`SELECT
COUNT(*) AS total,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS active,
SUM(CASE WHEN balance_cents < 0 THEN 1 ELSE 0 END) AS lowBalance,
SUM(balance_cents) AS totalBalanceCents
FROM auth_users`,
);
return { users: { total: Number(row.total ?? 0), lowBalance: Number(row.lowBalance ?? 0) }, routes: { total: 0, active: 0 } };
return {
users: {
total: Number(row.total ?? 0),
active: Number(row.active ?? 0),
lowBalance: Number(row.lowBalance ?? 0),
totalBalanceCents: Number(row.totalBalanceCents ?? 0),
},
usage24h: { count: 0, costCents: 0 },
lowBalanceUsers: [],
recentUsage: [],
recentLedger: [],
llm: { keyCount: 0, selectedKeyName: null, globalModel: null },
};
}
return {
+33 -24
View File
@@ -5,10 +5,10 @@ import type { AdminDashboardSummary } from '../../types';
import { formatTime, formatYuan } from '../utils/format';
const QUICK_LINKS = [
{ to: '/admin/users', label: '用户管理', desc: '创建账号、启用禁用' },
{ to: '/admin/billing/recharge', label: '充值', desc: '为用户账户充值' },
{ to: '/admin/providers', label: 'LLM Provider', desc: '模型 Key 与全局配置' },
{ to: '/admin/capabilities', label: '能力权限', desc: '扩展与工具开关' },
{ to: '/users', label: '用户管理', desc: '创建账号、启用禁用' },
{ to: '/billing/recharge', label: '充值', desc: '为用户账户充值' },
{ to: '/providers', label: 'LLM Provider', desc: '模型 Key 与全局配置' },
{ to: '/capabilities', label: '能力权限', desc: '扩展与工具开关' },
] as const;
export function DashboardPage() {
@@ -32,6 +32,15 @@ export function DashboardPage() {
void load();
}, [load]);
const safeSummary: AdminDashboardSummary = summary ?? {
users: { total: 0, active: 0, lowBalance: 0, totalBalanceCents: 0 },
usage24h: { count: 0, costCents: 0 },
lowBalanceUsers: [],
recentUsage: [],
recentLedger: [],
llm: null,
};
return (
<div className="admin-page">
<div className="admin-page-head">
@@ -44,45 +53,45 @@ export function DashboardPage() {
<div className="admin-stat-grid">
<div className="admin-stat-card">
<div className="admin-stat-label"></div>
<div className="admin-stat-value">{loading ? '—' : summary?.users.total}</div>
<div className="muted"> {loading ? '—' : summary?.users.active}</div>
<div className="admin-stat-value">{loading ? '—' : safeSummary.users.total}</div>
<div className="muted"> {loading ? '—' : safeSummary.users.active}</div>
</div>
<div className="admin-stat-card">
<div className="admin-stat-label"></div>
<div className="admin-stat-value">{loading ? '—' : summary?.users.lowBalance}</div>
<div className="admin-stat-value">{loading ? '—' : safeSummary.users.lowBalance}</div>
<div className="muted"> ¥0</div>
</div>
<div className="admin-stat-card">
<div className="admin-stat-label"></div>
<div className="admin-stat-value">
{loading ? '—' : `¥${formatYuan(summary?.users.totalBalanceCents ?? 0)}`}
{loading ? '—' : `¥${formatYuan(safeSummary.users.totalBalanceCents)}`}
</div>
</div>
<div className="admin-stat-card">
<div className="admin-stat-label">24h </div>
<div className="admin-stat-value">{loading ? '—' : summary?.usage24h.count}</div>
<div className="admin-stat-value">{loading ? '—' : safeSummary.usage24h.count}</div>
<div className="muted">
¥{loading ? '—' : formatYuan(summary?.usage24h.costCents ?? 0)}
¥{loading ? '—' : formatYuan(safeSummary.usage24h.costCents)}
</div>
</div>
<div className="admin-stat-card">
<div className="admin-stat-label">LLM Provider</div>
<div className="admin-stat-value">{loading ? '—' : (summary?.llm?.keyCount ?? '—')}</div>
<div className="admin-stat-value">{loading ? '—' : (safeSummary.llm?.keyCount ?? '—')}</div>
<div className="muted">
{loading
? '—'
: summary?.llm?.selectedKeyName
? `当前 ${summary.llm.selectedKeyName}`
: safeSummary.llm?.selectedKeyName
? `当前 ${safeSummary.llm.selectedKeyName}`
: '未配置'}
</div>
</div>
</div>
{!loading && summary && summary.lowBalanceUsers.length > 0 && (
{!loading && safeSummary.lowBalanceUsers.length > 0 && (
<section className="admin-card">
<div className="admin-card-head">
<h2></h2>
<Link to="/admin/billing/recharge" className="ghost-btn admin-inline-link">
<Link to="/billing/recharge" className="ghost-btn admin-inline-link">
</Link>
</div>
@@ -96,7 +105,7 @@ export function DashboardPage() {
</tr>
</thead>
<tbody>
{summary.lowBalanceUsers.map((user) => (
{safeSummary.lowBalanceUsers.map((user) => (
<tr key={user.id}>
<td>
<div>{user.displayName}</div>
@@ -105,7 +114,7 @@ export function DashboardPage() {
<td>¥{formatYuan(user.balanceCents)}</td>
<td>
<Link
to={`/admin/users/${user.id}`}
to={`/users/${user.id}`}
className="ghost-btn admin-inline-link"
>
@@ -131,12 +140,12 @@ export function DashboardPage() {
</div>
</section>
{!loading && summary && (
{!loading && (
<>
<section className="admin-card">
<div className="admin-card-head">
<h2></h2>
<Link to="/admin/billing/usage" className="ghost-btn admin-inline-link">
<Link to="/billing/usage" className="ghost-btn admin-inline-link">
</Link>
</div>
@@ -151,14 +160,14 @@ export function DashboardPage() {
</tr>
</thead>
<tbody>
{summary.recentUsage.length === 0 ? (
{safeSummary.recentUsage.length === 0 ? (
<tr>
<td colSpan={4} className="muted">
</td>
</tr>
) : (
summary.recentUsage.map((row) => (
safeSummary.recentUsage.map((row) => (
<tr key={row.id}>
<td>{formatTime(row.createdAt)}</td>
<td>@{row.username}</td>
@@ -177,7 +186,7 @@ export function DashboardPage() {
<section className="admin-card">
<div className="admin-card-head">
<h2></h2>
<Link to="/admin/billing/ledger" className="ghost-btn admin-inline-link">
<Link to="/billing/ledger" className="ghost-btn admin-inline-link">
</Link>
</div>
@@ -192,14 +201,14 @@ export function DashboardPage() {
</tr>
</thead>
<tbody>
{summary.recentLedger.length === 0 ? (
{safeSummary.recentLedger.length === 0 ? (
<tr>
<td colSpan={4} className="muted">
</td>
</tr>
) : (
summary.recentLedger.map((row) => (
safeSummary.recentLedger.map((row) => (
<tr key={row.id}>
<td>{formatTime(row.createdAt)}</td>
<td>@{row.username}</td>