feat: establish NekoNest Cloud control and relay

This commit is contained in:
2026-08-12 23:25:43 +08:00
commit f27606b709
222 changed files with 71456 additions and 0 deletions
+283
View File
@@ -0,0 +1,283 @@
import { requireAdminViewer } from "../cloud-auth";
import { DashboardShell, PageHeading, StatusPill, formatDate } from "../components/Shells";
import { getAdminSnapshot, type AccountDeletionRequestRecord, type FeedbackCategory } from "@/db/repository";
import type { ControlPlaneContactState } from "@/db/device-control-plane";
import { deriveInvitationDisplayState } from "@/db/invitations";
import {
AccessRequestAction,
BetaAction,
ExemptionAction,
FeedbackAction,
GateAction,
IncidentAction,
IncidentResolveAction,
InvitationRevokeAction,
PurgeAction,
RetentionAction,
} from "./AdminActions";
export const dynamic = "force-dynamic";
const feedbackCategoryLabels: Record<FeedbackCategory, string> = {
connection_issue: "连接或接入",
bug: "功能异常",
suggestion: "使用建议",
other: "其他",
};
const daemonContactCopy: Record<
ControlPlaneContactState,
{ label: string; tone: "good" | "warn" | "danger" | "neutral" }
> = {
fresh: { label: "控制面正常", tone: "good" },
delayed: { label: "联系延迟", tone: "warn" },
stale: { label: "长时间未联系", tone: "danger" },
never: { label: "尚未联系", tone: "neutral" },
invalid: { label: "时间异常", tone: "danger" },
};
const deletionStatusCopy: Record<
AccountDeletionRequestRecord["status"],
{ label: string; tone: "good" | "warn" | "danger" | "neutral" }
> = {
requested: { label: "待核对", tone: "warn" },
cancelled: { label: "已撤回", tone: "neutral" },
processing: { label: "删除中", tone: "danger" },
relay_purged: { label: "Relay 已逻辑删除", tone: "good" },
};
const retentionStatusCopy = {
never_run: {
label: "尚未自动运行",
tone: "warn",
detail: "定时入口尚未留下首次执行证据;管理员仍可使用下方手工回退。",
},
running: {
label: "正在清理",
tone: "info",
detail: "自动任务已经取得单实例运行权,正在处理已到期技术记录。",
},
healthy: {
label: "自动清理正常",
tone: "good",
detail: "最近一次自动清理在预期时间窗内成功完成。",
},
overdue: {
label: "自动清理逾期",
tone: "warn",
detail: "最近成功已经超过 36 小时,请核对托管环境的定时触发器。",
},
failed: {
label: "自动清理失败",
tone: "danger",
detail: "最近一次自动清理失败;错误码已最小化保存,运行时错误会继续进入平台观测。",
},
stalled: {
label: "自动清理卡住",
tone: "danger",
detail: "任务运行超过 30 分钟;下一次触发可以安全接管,但应先核对 D1 与 Worker 状态。",
},
invalid: {
label: "清理记录异常",
tone: "danger",
detail: "运行状态包含无效时间、计数或触发来源,不能把它当作成功证据。",
},
} as const;
function formatPercent(value: number | null) {
return value === null ? "暂无样本" : `${value}%`;
}
function formatDuration(seconds: number | null) {
if (seconds === null) return "暂无样本";
if (seconds < 60) return `${seconds}`;
if (seconds < 3600) return `${Math.round(seconds / 60)} 分钟`;
if (seconds < 86400) return `${Math.round((seconds / 3600) * 10) / 10} 小时`;
return `${Math.round((seconds / 86400) * 10) / 10}`;
}
export default async function AdminPage() {
const viewer = await requireAdminViewer();
const snapshot = await getAdminSnapshot();
const deferredPaidGates = snapshot.gates.filter((gate) => gate.priority === "PAID").length;
const openFeedback = snapshot.feedback.filter((item) => item.status === "open");
const pendingAccessRequests = snapshot.accessRequests.filter((item) => item.status === "requested");
const pendingAccessAccountIds = new Set(pendingAccessRequests.map((item) => item.account_id));
const proactiveInvitationAccounts = snapshot.accounts.filter((account) => !pendingAccessAccountIds.has(account.id));
const invitations = snapshot.grants.filter((grant) => grant.source === "admin_exemption");
const activeIncidents = snapshot.incidents.filter((item) => item.status === "active");
const pendingDeletionRequests = snapshot.deletionRequests.filter((item) => item.status === "requested");
const daemonContactAttention = snapshot.hostContacts.delayed
+ snapshot.hostContacts.stale
+ snapshot.hostContacts.never
+ snapshot.hostContacts.invalid;
const daemonContactTone = snapshot.hostContacts.invalid || snapshot.hostContacts.stale
? "danger"
: daemonContactAttention
? "warn"
: snapshot.hostContacts.totalActive
? "good"
: "neutral";
const retentionStatus = retentionStatusCopy[snapshot.retention.state];
const retentionLastSuccess = snapshot.retention.state === "invalid"
? "记录异常"
: formatDate(snapshot.retention.lastSuccessAt, true);
return (
<DashboardShell viewer={viewer} active="/admin">
<div className="cloud-page admin-page">
<PageHeading eyebrow="PUBLIC BETA ADMIN / 公测后台" title="免费政策、接入资格与上线门禁。" description="当前只运营免费公测。每次管理变更仍需理由、幂等键和追加式审计;报价、订单和价格发布均保持关闭。" />
<section className="admin-metrics"><article><span> P0 </span><strong>{snapshot.blockedP0}</strong><small></small></article><article><span></span><strong>{snapshot.beta?.state === "active" ? "全部免费" : "已结束"}</strong><small>{snapshot.beta?.capacity_slots === null ? "每账户不按主机槽位限额" : `每账户最多 ${snapshot.beta?.capacity_slots ?? 0} 台主机`}</small></article><article><span></span><strong>{activeIncidents.length ? `${activeIncidents.length} 个事件` : "正常"}</strong><small></small></article><article><span></span><strong>{pendingAccessRequests.length}</strong><small> {openFeedback.length} {deferredPaidGates} </small></article></section>
<section className="panel full-panel operations-panel">
<div className="panel-heading">
<div><span>DAEMON CONTROL PLANE / </span><h2></h2></div>
<StatusPill tone={daemonContactTone}>{daemonContactAttention ? `${daemonContactAttention} 台需留意` : snapshot.hostContacts.totalActive ? "全部正常" : "暂无主机"}</StatusPill>
</div>
<p className="operations-intro"> Relay daemon sealed </p>
<div className="operations-grid daemon-contact-grid">
<article><span></span><strong>{snapshot.hostContacts.totalActive}</strong><small>{snapshot.hostContacts.versionUnknown} daemon </small></article>
<article><span></span><strong>{snapshot.hostContacts.fresh}</strong><small>使 Relay </small></article>
<article><span></span><strong>{snapshot.hostContacts.delayed}</strong><small></small></article>
<article><span></span><strong>{snapshot.hostContacts.stale}</strong><small> daemon </small></article>
<article><span></span><strong>{snapshot.hostContacts.never}</strong><small> Relay </small></article>
<article><span></span><strong>{snapshot.hostContacts.invalid}</strong><small></small></article>
</div>
<div className="daemon-contact-attention">
<h3></h3>
{snapshot.hostContacts.attentionHosts.length ? (
<div className="admin-gate-table">
{snapshot.hostContacts.attentionHosts.map((host) => {
const state = daemonContactCopy[host.contact_state];
return (
<article key={host.id}>
<span className="gate-priority">{host.os === "windows" ? "W" : "L"}</span>
<div><strong>{host.name}</strong><small>{host.email} · {host.id} · {host.daemon_version || "版本待上报"}</small></div>
<StatusPill tone={state.tone}>{state.label}</StatusPill>
<span>{host.control_plane_last_seen_at ? formatDate(host.control_plane_last_seen_at, true) : "没有成功查询"}</span>
</article>
);
})}
</div>
) : <div className="empty-inline"></div>}
</div>
<p className="operations-footnote"> 25 {formatDate(snapshot.hostContacts.generatedAt, true)}</p>
</section>
<section className="panel full-panel operations-panel">
<div className="panel-heading">
<div><span>ROLLING 30 DAYS / 30 </span><h2></h2></div>
<StatusPill tone="info"></StatusPill>
</div>
<p className="operations-intro"> relay </p>
<div className="operations-grid">
<article>
<span></span>
<strong>{snapshot.operations.accounts.newAccounts}</strong>
<small> {snapshot.operations.accounts.total} {snapshot.operations.accounts.withActiveHost} </small>
</article>
<article>
<span></span>
<strong>{snapshot.operations.accessRequests.submitted}</strong>
<small>{snapshot.operations.accessRequests.pending} {snapshot.operations.accessRequests.cancelled} </small>
</article>
<article>
<span></span>
<strong>{formatPercent(snapshot.operations.accessRequests.approvalRatePercent)}</strong>
<small>{snapshot.operations.accessRequests.approved}/{snapshot.operations.accessRequests.decided} {formatDuration(snapshot.operations.accessRequests.averageReviewSeconds)}</small>
</article>
<article>
<span></span>
<strong>{snapshot.operations.accessRequests.requestedSlotDemand} </strong>
<small> {snapshot.operations.accessRequests.averageRequestedSlots ?? "暂无样本"} Windows {snapshot.operations.accessRequests.windows}Linux {snapshot.operations.accessRequests.linux} {snapshot.operations.accessRequests.both}</small>
</article>
<article>
<span></span>
<strong>{formatPercent(snapshot.operations.accessRequests.postApprovalClaimRatePercent)}</strong>
<small>{snapshot.operations.accessRequests.approvedWithPostApprovalClaim}/{snapshot.operations.accessRequests.approved} relay </small>
</article>
<article>
<span></span>
<strong>{formatPercent(snapshot.operations.pairings.claimRatePercent)}</strong>
<small>{snapshot.operations.pairings.claimed}/{snapshot.operations.pairings.created} {formatDuration(snapshot.operations.pairings.averageClaimSeconds)}</small>
</article>
<article>
<span></span>
<strong>{snapshot.operations.pairings.attentionRequired}</strong>
<small>{snapshot.operations.pairings.waiting} {snapshot.operations.pairings.expired} {snapshot.operations.pairings.locked} {snapshot.operations.pairings.rejectedAttempts} {snapshot.operations.pairings.rateLimitedAttempts} </small>
</article>
<article>
<span>Relay placement </span>
<strong>{formatPercent(snapshot.operations.provisioning.successRatePercent)}</strong>
<small>{snapshot.operations.provisioning.succeeded}/{snapshot.operations.provisioning.created} placement active {formatDuration(snapshot.operations.provisioning.averageCompletionSeconds)}</small>
</article>
<article>
<span></span>
<strong>{formatPercent(snapshot.operations.support.resolutionRatePercent)}</strong>
<small>{snapshot.operations.support.resolved}/{snapshot.operations.support.created} {snapshot.operations.support.connectionIssues} {formatDuration(snapshot.operations.support.averageResolutionSeconds)}</small>
</article>
<article className="operations-unavailable">
<span></span>
<strong></strong>
<small>relay </small>
</article>
</div>
<p className="operations-footnote"> {snapshot.operations.windowDays} {formatDate(snapshot.operations.generatedAt, true)}</p>
</section>
<div className="admin-grid">
<section className="panel admin-module"><div className="panel-heading"><div><span></span><h2></h2></div><StatusPill tone={snapshot.beta?.state === "active" && snapshot.blockedP0 === 0 ? "good" : snapshot.beta?.state === "active" ? "warn" : "neutral"}>{snapshot.beta?.state === "active" && snapshot.blockedP0 > 0 ? "政策已设 / 接入冻结" : snapshot.beta?.state ?? "未设置"}</StatusPill></div><BetaAction beta={snapshot.beta} blockedP0={snapshot.blockedP0} /></section>
<section className="panel admin-module"><div className="panel-heading"><div><span></span><h2></h2></div><StatusPill tone="neutral"></StatusPill></div><p></p></section>
<section className="panel admin-module"><div className="panel-heading"><div><span> / </span><h2></h2></div></div><p></p><ExemptionAction accounts={proactiveInvitationAccounts} /></section>
<section className="panel admin-module">
<div className="panel-heading">
<div><span>DATA MINIMIZATION</span><h2></h2></div>
<StatusPill tone={retentionStatus.tone}>{retentionStatus.label}</StatusPill>
</div>
<p> 02:17 退</p>
<div className="maintenance-status" aria-label="到期数据自动清理状态">
<div><span></span><strong>{retentionLastSuccess}</strong></div>
<div><span></span><strong>{snapshot.retention.runCount}</strong></div>
<div><span></span><strong>{snapshot.retention.consecutiveFailures}</strong></div>
</div>
<p className="maintenance-status-detail">
{retentionStatus.detail}
{snapshot.retention.ageSeconds !== null ? ` 距相关状态约 ${formatDuration(snapshot.retention.ageSeconds)}` : ""}
{snapshot.retention.errorCode ? ` 错误码:${snapshot.retention.errorCode}` : ""}
</p>
<RetentionAction />
</section>
<section className="panel admin-module"><div className="panel-heading"><div><span></span><h2>线</h2></div></div><GateAction gates={snapshot.gates} /></section>
</div>
<section className="panel full-panel"><div className="panel-heading"><div><span>CLOSED BETA REQUESTS</span><h2></h2></div><StatusPill tone={pendingAccessRequests.length ? "warn" : "good"}>{pendingAccessRequests.length ? `${pendingAccessRequests.length} 条待处理` : "已清空"}</StatusPill></div><p> 13 </p><div className="admin-feedback-list">{snapshot.accessRequests.length ? snapshot.accessRequests.map((request) => { const account = snapshot.accounts.find((item) => item.id === request.account_id); return <article key={request.id}><div className="feedback-meta"><StatusPill tone={request.status === "approved" ? "good" : request.status === "requested" ? "warn" : "neutral"}>{request.status === "approved" ? "已批准" : request.status === "requested" ? "待处理" : request.status === "declined" ? "未批准" : "用户已撤回"}</StatusPill><span>{account?.email ?? request.account_id}</span><span>{request.requested_slots} · {request.preferred_os}</span><span>{formatDate(request.requested_at, true)}</span></div><p>{request.use_case}</p>{request.admin_response && <div className="feedback-response"><strong></strong><p>{request.admin_response}</p></div>}<code>{request.id}</code>{request.status === "requested" && <AccessRequestAction request={request} />}</article>; }) : <div className="empty-inline"></div>}</div></section>
<section className="panel full-panel"><div className="panel-heading"><div><span>CLOSED BETA INVITATIONS</span><h2></h2></div><StatusPill tone={invitations.some((grant) => deriveInvitationDisplayState(grant) === "active") ? "good" : "neutral"}>{invitations.filter((grant) => deriveInvitationDisplayState(grant) === "active").length} </StatusPill></div><p></p><div className="admin-feedback-list">{invitations.length ? invitations.map((invitation) => { const state = deriveInvitationDisplayState(invitation); const account = snapshot.accounts.find((item) => item.id === invitation.account_id); return <article key={invitation.id}><div className="feedback-meta"><StatusPill tone={state === "active" ? "good" : "neutral"}>{state === "active" ? "有效" : state === "expired" ? "已到期" : "已撤销"}</StatusPill><span>{account?.email ?? invitation.account_id}</span><span>{invitation.capacity_slots === null ? "不按槽位限额" : `${invitation.capacity_slots} 个槽位`}</span><span> {formatDate(invitation.ends_at, true)}</span></div><p>{invitation.reason}</p><code>{invitation.id}</code>{state === "active" && <InvitationRevokeAction invitation={invitation} />}</article>; }) : <div className="empty-inline"></div>}</div></section>
<section className="panel full-panel">
<div className="panel-heading">
<div><span>ACCOUNT LIFECYCLE</span><h2></h2></div>
<StatusPill tone={pendingDeletionRequests.length ? "warn" : "good"}>
{pendingDeletionRequests.length ? `${pendingDeletionRequests.length} 条待核对` : "无待核对"}
</StatusPill>
</div>
<p>Relay </p>
<div className="admin-deletion-list">
{snapshot.deletionRequests.length ? snapshot.deletionRequests.map((request) => {
const copy = deletionStatusCopy[request.status];
return (
<article key={request.id}>
<div>
<StatusPill tone={copy.tone}>{copy.label}</StatusPill>
<strong>{snapshot.accounts.find((account) => account.id === request.account_id)?.email ?? request.account_id}</strong>
<span>{formatDate(request.requested_at, true)}</span>
</div>
<p>{request.reason || "用户未填写补充说明。"}</p>
<code>{request.id}</code>
{request.status === "requested" && <PurgeAction request={request} />}
</article>
);
}) : <div className="empty-inline"></div>}
</div>
</section>
<section className="panel full-panel"><div className="panel-heading"><div><span>STATUS & INCIDENTS</span><h2></h2></div><StatusPill tone={activeIncidents.length ? "danger" : "good"}>{activeIncidents.length ? `${activeIncidents.length} 个处理中` : "服务正常"}</StatusPill></div><div className="admin-incident-layout"><div><h3></h3><p></p><IncidentAction /></div><div className="admin-incident-list"><h3></h3>{snapshot.incidents.length ? snapshot.incidents.map((incident) => <article key={incident.id}><div className="incident-card-heading"><StatusPill tone={incident.status === "resolved" ? "good" : incident.severity === "outage" ? "danger" : incident.severity === "degraded" ? "warn" : "info"}>{incident.status === "resolved" ? "已恢复" : incident.severity === "outage" ? "服务中断" : incident.severity === "degraded" ? "服务降级" : "计划维护"}</StatusPill><span>{formatDate(incident.started_at, true)}</span></div><h4>{incident.title}</h4><p>{incident.message}</p>{incident.status === "active" ? <IncidentResolveAction incident={incident} /> : <div className="feedback-response"><strong></strong><p>{incident.resolution}</p></div>}</article>) : <div className="empty-inline"></div>}</div></div></section>
<section className="panel full-panel"><div className="panel-heading"><div><span>FREE BETA SUPPORT</span><h2></h2></div><StatusPill tone={openFeedback.length ? "warn" : "good"}>{openFeedback.length ? `${openFeedback.length} 条待处理` : "已清空"}</StatusPill></div>{snapshot.feedback.length ? <div className="admin-feedback-list">{snapshot.feedback.map((item) => <article key={item.id}><div className="feedback-meta"><StatusPill tone={item.status === "resolved" ? "good" : "warn"}>{item.status === "resolved" ? "已回复" : "待处理"}</StatusPill><span>{feedbackCategoryLabels[item.category]}</span><span>{formatDate(item.created_at, true)}</span><span>{snapshot.accounts.find((account) => account.id === item.account_id)?.email ?? item.account_id}</span></div><p>{item.message}</p>{item.admin_response && <div className="feedback-response"><strong></strong><p>{item.admin_response}</p></div>}{item.status === "open" && <FeedbackAction feedback={item} />}</article>)}</div> : <div className="empty-inline"></div>}</section>
<section className="panel full-panel"><div className="panel-heading"><div><span>P0 / P1 / PAID</span><h2></h2></div></div><div className="admin-gate-table">{snapshot.gates.map((gate) => <article key={gate.key}><span className="gate-priority">{gate.priority}</span><div><strong>{gate.title}</strong><small>{gate.category} · {gate.key}</small></div><StatusPill tone={gate.priority === "PAID" ? "neutral" : gate.status === "passed" ? "good" : gate.status === "in_progress" ? "warn" : "danger"}>{gate.priority === "PAID" ? "以后处理" : gate.status}</StatusPill><span>{gate.owner || "待指定"}</span></article>)}</div></section>
<section className="panel full-panel"><div className="panel-heading"><div><span>APPEND ONLY</span><h2></h2></div></div><div className="audit-list">{snapshot.audits.length ? snapshot.audits.map((audit) => <article key={audit.id}><span>{formatDate(audit.created_at, true)}</span><strong>{audit.action}</strong><span>{audit.target_type} / {audit.target_id}</span><p>{audit.reason}</p><code>{audit.actor_id}</code></article>) : <div className="empty-inline"></div>}</div></section>
</div>
</DashboardShell>
);
}