feat(admin): add operations resources and audit UI
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
import AdministratorsResource from "@/components/admin/administrators-resource";
|
||||
export default function Page() { return <AdministratorsResource />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import AuditLogsResource from "@/components/admin/audit-logs-resource";
|
||||
export default function Page() { return <AuditLogsResource />; }
|
||||
@@ -1,27 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
import AuditLogsResource from "@/components/admin/audit-logs-resource";
|
||||
import CodesResource from "@/components/admin/codes-resource";
|
||||
import ConsultationsResource from "@/components/admin/consultations-resource";
|
||||
import CreditTransactionsResource from "@/components/admin/credit-transactions-resource";
|
||||
import UsersResource from "@/components/admin/users-resource";
|
||||
|
||||
const resourceComponents = {
|
||||
codes: CodesResource,
|
||||
users: UsersResource,
|
||||
"credit-transactions": CreditTransactionsResource,
|
||||
consultations: ConsultationsResource,
|
||||
"audit-logs": AuditLogsResource,
|
||||
} as const;
|
||||
|
||||
export default function AdminResourcesPage() {
|
||||
const requested = useSearchParams().get("resource") ?? "codes";
|
||||
const Resource = resourceComponents[
|
||||
requested in resourceComponents
|
||||
? requested as keyof typeof resourceComponents
|
||||
: "codes"
|
||||
];
|
||||
return <Resource />;
|
||||
export default function AdminCodesPage() {
|
||||
return <CodesResource />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
import ConsultationsResource from "@/components/admin/consultations-resource";
|
||||
export default function Page() { return <ConsultationsResource />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import CreditTransactionsResource from "@/components/admin/credit-transactions-resource";
|
||||
export default function Page() { return <CreditTransactionsResource />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import UsersResource from "@/components/admin/users-resource";
|
||||
export default function Page() { return <UsersResource />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import FeatureFlagsManagement from "@/components/admin/feature-flags-management";
|
||||
export default function Page() { return <FeatureFlagsManagement />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import { ModelReleasesResource } from "@/components/admin/billing-operations-resources";
|
||||
export default function Page() { return <ModelReleasesResource />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import ModelManagement from "@/components/admin/model-management";
|
||||
export default function Page() { return <ModelManagement />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import { OrdersResource } from "@/components/admin/billing-operations-resources";
|
||||
export default function Page() { return <OrdersResource />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import ProductManagement from "@/components/admin/product-management";
|
||||
export default function Page() { return <ProductManagement />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import RolesResource from "@/components/admin/roles-resource";
|
||||
export default function Page() { return <RolesResource />; }
|
||||
@@ -0,0 +1,5 @@
|
||||
import MfaSecurity from "@/components/admin/mfa-security";
|
||||
|
||||
export default function Page() {
|
||||
return <MfaSecurity />;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import { SubscriptionsResource } from "@/components/admin/billing-operations-resources";
|
||||
export default function Page() { return <SubscriptionsResource />; }
|
||||
@@ -0,0 +1,2 @@
|
||||
import { UsageResource } from "@/components/admin/billing-operations-resources";
|
||||
export default function Page() { return <UsageResource />; }
|
||||
@@ -1,33 +1,5 @@
|
||||
"use client";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import Link from "next/link";
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
|
||||
type AdminUser = { userId: string | null; email: string | null; createdAt: string | null; source: "env" | "database" };
|
||||
|
||||
async function loadUsers(): Promise<AdminUser[]> {
|
||||
const response = await fetch("/api/admin/users", { cache: "no-store" });
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) throw new Error(payload?.error || "暂时无法读取管理员列表");
|
||||
return payload.users;
|
||||
}
|
||||
|
||||
export default function AdminUsersPage() {
|
||||
const [users, setUsers] = useState<AdminUser[]>([]);
|
||||
const [email, setEmail] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
useEffect(() => { void loadUsers().then(setUsers).catch((caught) => setError(caught.message)); }, []);
|
||||
async function add(event: FormEvent) {
|
||||
event.preventDefault(); setError("");
|
||||
const response = await fetch("/api/admin/users", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email }) });
|
||||
const payload = await response.json().catch(() => null);
|
||||
if (!response.ok) { setError(payload?.error || "添加失败"); return; }
|
||||
setEmail(""); setUsers(await loadUsers());
|
||||
}
|
||||
async function revoke(userId: string) {
|
||||
const response = await fetch("/api/admin/users", { method: "DELETE", headers: { "content-type": "application/json" }, body: JSON.stringify({ userId }) });
|
||||
if (!response.ok) { const payload = await response.json().catch(() => null); setError(payload?.error || "撤销失败"); return; }
|
||||
setUsers(await loadUsers());
|
||||
}
|
||||
return <main className="standalone-page admin-page"><header className="admin-header"><h1>管理员管理</h1><Link className="button-secondary" href="/admin/codes">兑换码管理</Link></header><div className="admin-scroll"><section className="admin-section"><div className="section-title"><div><h2>添加管理员</h2><p>仅能添加已经注册的 Supabase 用户。</p></div></div><form className="code-form" onSubmit={add}><label className="note-field"><span>邮箱</span><input type="email" required value={email} onChange={(event) => setEmail(event.target.value)} /></label><button className="button-primary" type="submit">添加</button></form>{error && <p className="form-error" role="alert">{error}</p>}</section><section className="admin-section"><div className="section-title"><div><h2>当前管理员</h2><p>{users.length} 位</p></div></div><div className="admin-table-wrap"><table className="admin-table"><thead><tr><th>邮箱</th><th>来源</th><th>添加时间</th><th>操作</th></tr></thead><tbody>{users.map((user) => <tr key={`${user.source}-${user.userId ?? user.email}`}><td>{user.email || "—"}</td><td>{user.source === "env" ? "环境配置" : "后台配置"}</td><td>{user.createdAt ? new Date(user.createdAt).toLocaleString("zh-CN") : "—"}</td><td>{user.userId && <button className="button-secondary" type="button" onClick={() => void revoke(user.userId!)}>撤销</button>}</td></tr>)}</tbody></table></div></section></div></main>;
|
||||
export default function LegacyUsersPage() {
|
||||
redirect("/admin/customers");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user