diff --git a/admin-frontend/src/api.ts b/admin-frontend/src/api.ts index 83be9ec..16dbb38 100644 --- a/admin-frontend/src/api.ts +++ b/admin-frontend/src/api.ts @@ -168,6 +168,7 @@ export const api = { owner_email?: string; tier?: string; rate_limit_per_min?: number; + quota_total?: number; }) => request<{ key: string; item: import("./types").ApiKey; warning: string }>( "/keys", diff --git a/admin-frontend/src/components/ApiKeysPage.tsx b/admin-frontend/src/components/ApiKeysPage.tsx index 6eeb021..4b02ff4 100644 --- a/admin-frontend/src/components/ApiKeysPage.tsx +++ b/admin-frontend/src/components/ApiKeysPage.tsx @@ -4,9 +4,10 @@ import type { ApiKey } from "../types"; import { Copy, KeyRound, Plus, Trash2 } from "lucide-react"; const TIERS = [ - { key: "free", label: "免费 (free)", rate: 120 }, - { key: "partner", label: "合作方 (partner)", rate: 600 }, - { key: "internal", label: "内部 (internal)", rate: 6000 }, + { key: "free", label: "免费 (free)", rate: 120, quota: 1000 }, + { key: "registered", label: "注册用户 (registered)", rate: 300, quota: 100000 }, + { key: "partner", label: "合作方 (partner)", rate: 600, quota: 0 }, + { key: "internal", label: "内部 (internal)", rate: 6000, quota: 0 }, ]; function tierLabel(tier: string): string { @@ -111,6 +112,7 @@ export default function ApiKeysPage() { 级别 速率/分钟 用量(今日/累计) + 累计配额 状态 @@ -118,7 +120,7 @@ export default function ApiKeysPage() { {rows.length === 0 ? ( - + 暂无密钥 @@ -139,6 +141,15 @@ export default function ApiKeysPage() { {k.usage.today} / {k.usage.total} + + {k.quota_total > 0 ? ( + = k.quota_total ? "text-red-600" : ""}> + {k.usage.total.toLocaleString()} / {k.quota_total.toLocaleString()} + + ) : ( + 不限 + )} + {k.revoked_at ? ( @@ -182,13 +193,17 @@ function CreateKeyForm({ const [ownerEmail, setOwnerEmail] = useState(""); const [tier, setTier] = useState("free"); const [rate, setRate] = useState(120); + const [quota, setQuota] = useState(1000); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); function pickTier(t: string) { setTier(t); const def = TIERS.find((x) => x.key === t); - if (def) setRate(def.rate); + if (def) { + setRate(def.rate); + setQuota(def.quota); + } } async function submit() { @@ -204,6 +219,7 @@ function CreateKeyForm({ owner_email: ownerEmail.trim() || undefined, tier, rate_limit_per_min: rate, + quota_total: quota, }); onCreated(res.key); } catch (e) { @@ -260,6 +276,16 @@ function CreateKeyForm({ onChange={(e) => setRate(Math.max(1, parseInt(e.target.value || "1", 10)))} /> +
+
@@ -77,7 +87,8 @@ export default function App() { {view.name === "contribute" && ( setView({ name: "home" })} /> )} - {view.name === "api" && } + {view.name === "api" && setView({ name: "account" })} />} + {view.name === "account" && }