import { THEME_CSS, escapeHtml, escapeAttr, tabNavHTML } from "../render/layout"; import { parsePatIpAllowlist, type PatWithUser, type UserRow } from "../store"; interface Flash { kind: "ok" | "err"; msg: string; } function statusBadge(row: PatWithUser): string { if (row.revoked_at) return `已吊销`; if (row.expires_at && Date.parse(row.expires_at) < Date.now()) { return `已过期`; } return `生效中`; } function parseScopes(raw: string): string[] { try { const v = JSON.parse(raw); return Array.isArray(v) ? v.filter((x) => typeof x === "string") : []; } catch { return []; } } export function buildAdminTokensPage( viewer: UserRow, rows: PatWithUser[], flash: Flash | null, ): string { const flashHtml = flash ? `
${escapeHtml(flash.msg)}
` : ""; const activeCount = rows.filter((r) => !r.revoked_at && !(r.expires_at && Date.parse(r.expires_at) < Date.now())).length; const revokedCount = rows.length - activeCount; const rowsHtml = rows.length ? ` ${rows .map((r) => { const lastUsed = r.last_used_at ? escapeHtml(r.last_used_at.slice(0, 16).replace("T", " ")) : "—"; const scopes = parseScopes(r.scopes).map((s) => `${escapeHtml(s)}`).join(" ") || "—"; const ipList = parsePatIpAllowlist(r.ip_allowlist ?? "[]"); const ipCell = ipList.length === 0 ? `全部` : ipList.map((c) => `${escapeHtml(c)}`).join(" "); const userBadges: string[] = []; if (r.user_is_admin === 1) userBadges.push(`admin`); if (r.user_kind === "bot") userBadges.push(`bot`); else if (r.user_kind === "system") userBadges.push(`system`); if (r.user_is_active === 0) userBadges.push(`禁用`); const revoked = !!r.revoked_at; const ownerNote = r.user_login === viewer.login ? `(你)` : ""; return ``; }) .join("")}
名称用户tokenscopesIP状态最近使用创建操作
${escapeHtml(r.name)} ${escapeHtml(r.user_login)} ${ownerNote} ${userBadges.join(" ")} …${escapeHtml(r.token_last_eight)} ${scopes} ${ipCell} ${statusBadge(r)} ${lastUsed} ${escapeHtml(r.created_at.slice(0, 10))} ${ revoked ? "" : `
` }
` : `
系统里还没有任何 PAT。
`; return ` Token 管理 · ework
🔑 Token 管理(管理员)
${tabNavHTML("projects", { login: viewer.login, is_admin: viewer.is_admin })}

所有 Personal Access Tokens

共 ${rows.length} 个(生效中 ${activeCount},已吊销/过期 ${revokedCount})

${flashHtml}

Token 列表

${rowsHtml}
吊销任意 token 后,使用该 token 的 agent / CLI 会立即失去权限(verifyPat 检查 revoked_at)。用户自己的 token 仍由 /me/tokens 自助管理。
`; }