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() { async function getAdminSummary() {
const [[row]] = await pool.execute( 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 { return {
+33 -24
View File
@@ -5,10 +5,10 @@ import type { AdminDashboardSummary } from '../../types';
import { formatTime, formatYuan } from '../utils/format'; import { formatTime, formatYuan } from '../utils/format';
const QUICK_LINKS = [ const QUICK_LINKS = [
{ to: '/admin/users', label: '用户管理', desc: '创建账号、启用禁用' }, { to: '/users', label: '用户管理', desc: '创建账号、启用禁用' },
{ to: '/admin/billing/recharge', label: '充值', desc: '为用户账户充值' }, { to: '/billing/recharge', label: '充值', desc: '为用户账户充值' },
{ to: '/admin/providers', label: 'LLM Provider', desc: '模型 Key 与全局配置' }, { to: '/providers', label: 'LLM Provider', desc: '模型 Key 与全局配置' },
{ to: '/admin/capabilities', label: '能力权限', desc: '扩展与工具开关' }, { to: '/capabilities', label: '能力权限', desc: '扩展与工具开关' },
] as const; ] as const;
export function DashboardPage() { export function DashboardPage() {
@@ -32,6 +32,15 @@ export function DashboardPage() {
void load(); void load();
}, [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 ( return (
<div className="admin-page"> <div className="admin-page">
<div className="admin-page-head"> <div className="admin-page-head">
@@ -44,45 +53,45 @@ export function DashboardPage() {
<div className="admin-stat-grid"> <div className="admin-stat-grid">
<div className="admin-stat-card"> <div className="admin-stat-card">
<div className="admin-stat-label"></div> <div className="admin-stat-label"></div>
<div className="admin-stat-value">{loading ? '—' : summary?.users.total}</div> <div className="admin-stat-value">{loading ? '—' : safeSummary.users.total}</div>
<div className="muted"> {loading ? '—' : summary?.users.active}</div> <div className="muted"> {loading ? '—' : safeSummary.users.active}</div>
</div> </div>
<div className="admin-stat-card"> <div className="admin-stat-card">
<div className="admin-stat-label"></div> <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 className="muted"> ¥0</div>
</div> </div>
<div className="admin-stat-card"> <div className="admin-stat-card">
<div className="admin-stat-label"></div> <div className="admin-stat-label"></div>
<div className="admin-stat-value"> <div className="admin-stat-value">
{loading ? '—' : `¥${formatYuan(summary?.users.totalBalanceCents ?? 0)}`} {loading ? '—' : `¥${formatYuan(safeSummary.users.totalBalanceCents)}`}
</div> </div>
</div> </div>
<div className="admin-stat-card"> <div className="admin-stat-card">
<div className="admin-stat-label">24h </div> <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"> <div className="muted">
¥{loading ? '—' : formatYuan(summary?.usage24h.costCents ?? 0)} ¥{loading ? '—' : formatYuan(safeSummary.usage24h.costCents)}
</div> </div>
</div> </div>
<div className="admin-stat-card"> <div className="admin-stat-card">
<div className="admin-stat-label">LLM Provider</div> <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"> <div className="muted">
{loading {loading
? '—' ? '—'
: summary?.llm?.selectedKeyName : safeSummary.llm?.selectedKeyName
? `当前 ${summary.llm.selectedKeyName}` ? `当前 ${safeSummary.llm.selectedKeyName}`
: '未配置'} : '未配置'}
</div> </div>
</div> </div>
</div> </div>
{!loading && summary && summary.lowBalanceUsers.length > 0 && ( {!loading && safeSummary.lowBalanceUsers.length > 0 && (
<section className="admin-card"> <section className="admin-card">
<div className="admin-card-head"> <div className="admin-card-head">
<h2></h2> <h2></h2>
<Link to="/admin/billing/recharge" className="ghost-btn admin-inline-link"> <Link to="/billing/recharge" className="ghost-btn admin-inline-link">
</Link> </Link>
</div> </div>
@@ -96,7 +105,7 @@ export function DashboardPage() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{summary.lowBalanceUsers.map((user) => ( {safeSummary.lowBalanceUsers.map((user) => (
<tr key={user.id}> <tr key={user.id}>
<td> <td>
<div>{user.displayName}</div> <div>{user.displayName}</div>
@@ -105,7 +114,7 @@ export function DashboardPage() {
<td>¥{formatYuan(user.balanceCents)}</td> <td>¥{formatYuan(user.balanceCents)}</td>
<td> <td>
<Link <Link
to={`/admin/users/${user.id}`} to={`/users/${user.id}`}
className="ghost-btn admin-inline-link" className="ghost-btn admin-inline-link"
> >
@@ -131,12 +140,12 @@ export function DashboardPage() {
</div> </div>
</section> </section>
{!loading && summary && ( {!loading && (
<> <>
<section className="admin-card"> <section className="admin-card">
<div className="admin-card-head"> <div className="admin-card-head">
<h2></h2> <h2></h2>
<Link to="/admin/billing/usage" className="ghost-btn admin-inline-link"> <Link to="/billing/usage" className="ghost-btn admin-inline-link">
</Link> </Link>
</div> </div>
@@ -151,14 +160,14 @@ export function DashboardPage() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{summary.recentUsage.length === 0 ? ( {safeSummary.recentUsage.length === 0 ? (
<tr> <tr>
<td colSpan={4} className="muted"> <td colSpan={4} className="muted">
</td> </td>
</tr> </tr>
) : ( ) : (
summary.recentUsage.map((row) => ( safeSummary.recentUsage.map((row) => (
<tr key={row.id}> <tr key={row.id}>
<td>{formatTime(row.createdAt)}</td> <td>{formatTime(row.createdAt)}</td>
<td>@{row.username}</td> <td>@{row.username}</td>
@@ -177,7 +186,7 @@ export function DashboardPage() {
<section className="admin-card"> <section className="admin-card">
<div className="admin-card-head"> <div className="admin-card-head">
<h2></h2> <h2></h2>
<Link to="/admin/billing/ledger" className="ghost-btn admin-inline-link"> <Link to="/billing/ledger" className="ghost-btn admin-inline-link">
</Link> </Link>
</div> </div>
@@ -192,14 +201,14 @@ export function DashboardPage() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{summary.recentLedger.length === 0 ? ( {safeSummary.recentLedger.length === 0 ? (
<tr> <tr>
<td colSpan={4} className="muted"> <td colSpan={4} className="muted">
</td> </td>
</tr> </tr>
) : ( ) : (
summary.recentLedger.map((row) => ( safeSummary.recentLedger.map((row) => (
<tr key={row.id}> <tr key={row.id}>
<td>{formatTime(row.createdAt)}</td> <td>{formatTime(row.createdAt)}</td>
<td>@{row.username}</td> <td>@{row.username}</td>