feat: harden tenant access and production operations

This commit is contained in:
xz
2026-08-24 22:02:22 +08:00
parent ffb27d845b
commit 9811469a64
18 changed files with 709 additions and 36 deletions
+62 -9
View File
@@ -1119,31 +1119,77 @@ function UsageTrendCard({ rows }) {
);
}
export function AdminOverviewPage({ platformContext, setActiveTab }) {
export function AdminOverviewPage({ platformContext, contextOverrides = {}, setActiveTab }) {
const platform = platformContext?.platform || {};
const summary = platformContext?.summary || {};
const permissions = new Set(platformContext?.context?.permissions || []);
const canViewSystem = Boolean(platformContext?.context?.systemAdmin) || permissions.has("system:settings:view");
const canManageQueue = permissions.has("queue:manage");
const runners = platform.runnerHealth || [];
const members = platform.members || [];
const projects = platform.projects || [];
const usage = platform.usage || {};
const attention = runners.filter((runner) => runner.status !== "ready").length;
const [health, setHealth] = useState({ services: [], summary: {} });
const [worker, setWorker] = useState(null);
const [readiness, setReadiness] = useState(null);
const [loading, setLoading] = useState(true);
const [notice, setNotice] = useState("");
const scopeKey = [contextOverrides.userId, contextOverrides.organizationId, contextOverrides.workspaceId, contextOverrides.projectId].join("|");
async function loadOperations() {
setLoading(true);
setNotice("");
const [healthResult, workerResult, readinessResult] = await Promise.allSettled([
fetchSystemHealth(contextOverrides),
fetchWorkerStatus(contextOverrides),
canViewSystem ? fetchSystemReadiness(contextOverrides) : Promise.resolve(null)
]);
const errors = [];
if (healthResult.status === "fulfilled") setHealth(healthResult.value);
else errors.push(`系统健康:${healthResult.reason?.message || "读取失败"}`);
if (workerResult.status === "fulfilled") setWorker(workerResult.value.worker || workerResult.value);
else errors.push(`Worker:${workerResult.reason?.message || "读取失败"}`);
if (readinessResult.status === "fulfilled" && readinessResult.value) setReadiness(readinessResult.value);
else if (canViewSystem && readinessResult.status === "rejected") errors.push(`生产就绪度:${readinessResult.reason?.message || "读取失败"}`);
if (errors.length) setNotice(errors.join(";"));
setLoading(false);
}
useEffect(() => { loadOperations(); }, [scopeKey, canViewSystem]);
const services = health.services?.length ? health.services : (platform.runnerHealth || []).map((runner) => ({
id: runner.id,
service_key: runner.id,
label: runner.name,
kind: "runner",
status: runner.status,
queue_depth: runner.queueDepth,
last_heartbeat: runner.lastHeartbeat,
metadata: {}
}));
const liveSummary = health.summary?.total ? health.summary : summary;
const queueDepth = Number(worker?.queueDepth ?? liveSummary.queueDepth ?? summary.queueDepth ?? 0);
const attention = Number(liveSummary.attention ?? services.filter((service) => service.status !== "ready").length);
const workerStatus = worker?.healthStatus || worker?.status || "unknown";
const readinessChecks = readiness?.checks || [];
const statusLabel = (status) => ({ ready: "正常", configured: "已配置", "active-local": "本地运行", "not-configured": "未配置", "needs-config": "需配置", missing: "缺失", unsafe: "不安全", paused: "已暂停", stale: "已过期", starting: "启动中", draining: "排空中", "waiting-model": "等待模型" }[status] || status || "未知");
const statusTone = (status) => ["ready", "configured", "active-local"].includes(status) ? "active" : ["not-configured", "needs-config", "missing", "unsafe", "stale", "failed"].includes(status) ? "failed" : "needs-evidence";
return (
<div className="enterprise-page">
<AdminHeader eyebrow="ADMIN CONSOLE / OVERVIEW" title="管理概览" description="组织管理员在这里查看平台运行状态、团队协作、队列容量、用量和需要处理的治理事项。" action={<button className="subtle" onClick={() => window.location.reload()}><RefreshCw size={15} />刷新概览</button>} />
<AdminHeader eyebrow="ADMIN CONSOLE / OVERVIEW" title="管理概览" description="组织管理员在这里查看平台运行状态、团队协作、队列容量、用量和需要处理的治理事项。" action={<div className="header-actions"><StatusBadge status={loading ? "needs-evidence" : attention ? "needs-evidence" : "active"} label={loading ? "同步中" : attention ? `${attention} 项需要关注` : "运行正常"} /><button className="subtle" onClick={loadOperations} disabled={loading}><RefreshCw size={15} />{loading ? "同步中…" : "刷新概览"}</button></div>} />
{notice && <div className="inline-notice warn"><AlertTriangle size={15} /><span>{notice}</span></div>}
<div className="admin-kpi-grid">
<AdminKpi icon={Users} label="组织成员" value={members.length} detail="活跃成员与待邀请" tone="ok" />
<AdminKpi icon={Workflow} label="生产项目" value={projects.length} detail={`${summary.activeProjects ?? 0} 个制作中`} tone="neutral" />
<AdminKpi icon={Network} label="模型 / Runner" value={`${summary.modelCount || platform.modelRegistry?.length || 0} / ${summary.runnerCount || runners.length}`} detail={`${attention} 个需要关注`} tone={attention ? "warn" : "ok"} />
<AdminKpi icon={Gauge} label="队列深度" value={summary.queueDepth || 0} detail="本地任务等待处理" tone={summary.queueDepth ? "warn" : "ok"} />
<AdminKpi icon={Network} label="模型 / 服务" value={`${summary.modelCount || platform.modelRegistry?.length || 0} / ${liveSummary.total || services.length}`} detail={`${attention} 个需要关注`} tone={attention ? "warn" : "ok"} />
<AdminKpi icon={Gauge} label="队列深度" value={queueDepth} detail={worker ? `Worker ${statusLabel(workerStatus)}` : "本地任务等待处理"} tone={queueDepth ? "warn" : "ok"} />
</div>
<div className="enterprise-grid enterprise-grid-main">
<section className="studio-card wide-card">
<SectionBar title="平台运营状态" detail="当前组织 · 当前工作区" action={canManageQueue && <ContextLink onClick={() => setActiveTab("admin-queue")}>打开队列</ContextLink>} />
<SectionBar title="平台运营状态" detail={health.checkedAt ? `真实服务探针 · ${formatAdminDate(health.checkedAt)}` : "当前组织 · 当前工作区"} action={canManageQueue && <ContextLink onClick={() => setActiveTab("admin-queue")}>打开队列</ContextLink>} />
<div className="service-overview-list">
{runners.map((runner) => <div className="service-overview-row" key={runner.id}><div className="service-name"><span className={`service-dot ${runner.status === "ready" ? "ok" : "warn"}`} /><div><strong>{runner.name}</strong><span>{runner.lastHeartbeat || "local"}</span></div></div><div className="service-meter"><div><span style={{ width: `${Math.min(100, 18 + runner.queueDepth * 16)}%` }} /></div><small>queue {runner.queueDepth}</small></div><StatusBadge status={runner.status} label={runner.status === "ready" ? "正常" : runner.status === "planned" ? "规划中" : "待接入模型"} /></div>)}
{services.map((service) => <div className="service-overview-row" key={service.id || service.service_key}><div className="service-name"><span className={`service-dot ${service.status === "ready" ? "ok" : "warn"}`} /><div><strong>{service.label || service.service_key}</strong><span>{service.kind} · {service.last_heartbeat || service.updated_at || "未上报心跳"}</span></div></div><div className="service-meter"><div><span style={{ width: `${Math.min(100, 18 + Number(service.queue_depth || 0) * 16)}%` }} /></div><small>queue {Number(service.queue_depth || 0)}</small></div><StatusBadge status={statusTone(service.status)} label={statusLabel(service.status)} /></div>)}
{worker && <div className="service-overview-row"><div className="service-name"><span className={`service-dot ${workerStatus === "ready" ? "ok" : "warn"}`} /><div><strong>本地 Worker · {worker.workerId || "default"}</strong><span>并发 {worker.maxConcurrency || 0} · 心跳 {worker.lastHeartbeatAt || "未上报"}</span></div></div><div className="service-meter"><div><span style={{ width: `${Math.min(100, Number(worker.inFlight || 0) * 34 + 12)}%` }} /></div><small>{worker.inFlight || 0} 执行中</small></div><StatusBadge status={statusTone(workerStatus)} label={statusLabel(workerStatus)} /></div>}
{!services.length && !worker && <div className="empty-table">还没有可读取的服务健康记录。</div>}
</div>
</section>
<section className="studio-card">
@@ -1151,7 +1197,7 @@ export function AdminOverviewPage({ platformContext, setActiveTab }) {
<div className="attention-list">
<div><ShieldAlert size={17} /><div><strong>外部云连接已禁用</strong><span>符合 local-runner-only 策略</span></div></div>
<div><AlertTriangle size={17} /><div><strong>声音授权证据缺失</strong><span>1 个策略需要补充材料</span></div></div>
<div><Clock3 size={17} /><div><strong>{summary.queueDepth || 0} 个任务排队</strong><span>可以调整队列并发或优先级</span></div></div>
<div><Clock3 size={17} /><div><strong>{queueDepth} 个任务排队</strong><span>可以调整队列并发或优先级</span></div></div>
</div>
{canViewSystem && <button className="subtle full-width" onClick={() => setActiveTab("system-generation")}><Settings2 size={15} />查看系统策略</button>}
</section>
@@ -1160,6 +1206,13 @@ export function AdminOverviewPage({ platformContext, setActiveTab }) {
<div className="usage-hero"><strong>{usage.totalCost ?? 86}</strong><span>本月本地计量成本</span></div>
{(usage.quotas || []).slice(0, 3).map((quota) => <div className="quota-row" key={`${quota.metric}-${quota.unit}`}><div><span>{quota.metric}</span><b>{quota.used_value}/{quota.limit_value} {quota.unit}</b></div><div className="quota-track"><span style={{ width: `${Math.min(100, Math.round((quota.used_value / quota.limit_value) * 100))}%` }} /></div></div>)}
</section>
{canViewSystem && <section className="studio-card wide-card">
<SectionBar title="生产就绪度" detail={readiness ? `${readiness.profile} · 检查于 ${formatAdminDate(readiness.checkedAt)}` : "系统管理员可查看部署前置条件"} action={<ContextLink onClick={() => setActiveTab("system-storage")}>查看系统设置</ContextLink>} />
<div className="policy-check-list">
{readinessChecks.slice(0, 8).map((check) => <div key={check.key}><ShieldCheck size={17} /><span>{check.label}<small>{check.detail}</small></span><StatusBadge status={statusTone(check.status)} label={statusLabel(check.status)} /></div>)}
{!readiness && !loading && <div className="empty-table">当前没有生产就绪度结果。</div>}
</div>
</section>}
</div>
</div>
);