Add LAN deploy scripts and improve admin billing/users UX.

Provide rsync deploy and restart tooling for the 100 server, document the workflow, and add pagination plus user filtering across billing and user management pages.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-06-16 20:46:59 +08:00
parent 8ad1a8a8da
commit 680e2c9427
11 changed files with 1079 additions and 275 deletions
+59 -11
View File
@@ -156,6 +156,16 @@ export async function logout(): Promise<void> {
await fetch('/auth/logout', { method: 'POST' });
}
// ── Pagination helpers ────────────────────────────────
export type PagedResult<T> = {
items: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
};
// ── Admin dashboard ───────────────────────────────────
export async function getAdminDashboardSummary(): Promise<AdminDashboardSummary> {
@@ -163,23 +173,61 @@ export async function getAdminDashboardSummary(): Promise<AdminDashboardSummary>
return result.summary;
}
export async function listAdminUsage(userId?: string): Promise<UsageRecord[]> {
const query = userId ? `?userId=${encodeURIComponent(userId)}` : '';
const result = await portalFetch<{ records: UsageRecord[] }>(`/admin-api/usage${query}`);
return result.records ?? [];
export async function listAdminUsage(params?: {
userId?: string;
page?: number;
pageSize?: number;
}): Promise<PagedResult<UsageRecord>> {
const q = new URLSearchParams();
if (params?.userId) q.set('userId', params.userId);
if (params?.page) q.set('page', String(params.page));
if (params?.pageSize) q.set('pageSize', String(params.pageSize));
const result = await portalFetch<{ records: UsageRecord[]; total: number; page: number; pageSize: number }>(
`/admin-api/usage${q.toString() ? `?${q}` : ''}`,
);
const total = result.total ?? 0;
const pageSize = result.pageSize ?? 50;
return { items: result.records ?? [], total, page: result.page ?? 1, pageSize, totalPages: Math.ceil(total / pageSize) };
}
export async function listAdminLedger(userId?: string): Promise<LedgerEntry[]> {
const query = userId ? `?userId=${encodeURIComponent(userId)}` : '';
const result = await portalFetch<{ entries: LedgerEntry[] }>(`/admin-api/ledger${query}`);
return result.entries ?? [];
export async function listAdminLedger(params?: {
userId?: string;
page?: number;
pageSize?: number;
}): Promise<PagedResult<LedgerEntry>> {
const q = new URLSearchParams();
if (params?.userId) q.set('userId', params.userId);
if (params?.page) q.set('page', String(params.page));
if (params?.pageSize) q.set('pageSize', String(params.pageSize));
const result = await portalFetch<{ entries: LedgerEntry[]; total: number; page: number; pageSize: number }>(
`/admin-api/ledger${q.toString() ? `?${q}` : ''}`,
);
const total = result.total ?? 0;
const pageSize = result.pageSize ?? 50;
return { items: result.entries ?? [], total, page: result.page ?? 1, pageSize, totalPages: Math.ceil(total / pageSize) };
}
// ── Admin users ───────────────────────────────────────
export async function listAdminUsers(): Promise<AdminUserRow[]> {
const result = await portalFetch<{ users: AdminUserRow[] }>('/admin-api/users');
return result.users ?? [];
export async function listAdminUsers(params?: {
page?: number;
pageSize?: number;
search?: string;
role?: string;
status?: string;
}): Promise<PagedResult<AdminUserRow>> {
const q = new URLSearchParams();
if (params?.page) q.set('page', String(params.page));
if (params?.pageSize) q.set('pageSize', String(params.pageSize));
if (params?.search) q.set('search', params.search);
if (params?.role) q.set('role', params.role);
if (params?.status) q.set('status', params.status);
const result = await portalFetch<{ users: AdminUserRow[]; total: number; page: number; pageSize: number }>(
`/admin-api/users${q.toString() ? `?${q}` : ''}`,
);
const total = result.total ?? 0;
const pageSize = result.pageSize ?? 20;
return { items: result.users ?? [], total, page: result.page ?? 1, pageSize, totalPages: Math.ceil(total / pageSize) };
}
export async function createAdminUser(payload: {