139 lines
8.1 KiB
TypeScript
139 lines
8.1 KiB
TypeScript
import Link from "next/link";
|
||
import { requireCloudViewer } from "../cloud-auth";
|
||
import {
|
||
DashboardShell,
|
||
PageHeading,
|
||
StatusPill,
|
||
formatDate,
|
||
} from "../components/Shells";
|
||
import { getDashboardSnapshot, getOrCreateAccount } from "@/db/repository";
|
||
import { getConnectionCopy } from "./connection-copy";
|
||
import { OpenPwaButton } from "./OpenPwaButton";
|
||
import { deriveControlPlaneContact } from "@/db/device-control-plane";
|
||
import { deriveBetaOnboarding } from "./onboarding";
|
||
|
||
export const dynamic = "force-dynamic";
|
||
|
||
export default async function DashboardPage() {
|
||
const viewer = await requireCloudViewer("/dashboard");
|
||
const account = await getOrCreateAccount(viewer);
|
||
const snapshot = await getDashboardSnapshot(account);
|
||
const activeHosts = snapshot.hosts.filter((host) => host.lifecycle === "active");
|
||
const entitlement = snapshot.entitlement;
|
||
const connection = getConnectionCopy(snapshot.connection.state);
|
||
const onboarding = deriveBetaOnboarding({
|
||
entitlement,
|
||
hasPendingRequest: snapshot.accessRequests.some((request) => request.status === "requested"),
|
||
});
|
||
const entitlementLabel = entitlement.mode === "public_beta"
|
||
? "公测免费"
|
||
: entitlement.mode === "grant"
|
||
? "闭测邀请"
|
||
: entitlement.publicBetaState === "gated"
|
||
? "等待安全门禁"
|
||
: "无有效权益";
|
||
const entitlementDetail = entitlement.mode === "none" && entitlement.publicBetaState === "gated"
|
||
? `公开接入仍有 ${entitlement.blockedP0} 项 P0 未通过;管理员邀请账户可继续闭测`
|
||
: entitlement.unlimited
|
||
? "费用全免;当前策略未按槽位限额"
|
||
: `可用 ${entitlement.availableSlots ?? 0} 个槽位`;
|
||
|
||
return (
|
||
<DashboardShell
|
||
viewer={viewer}
|
||
active="/dashboard"
|
||
serviceStatus={snapshot.serviceStatus}
|
||
>
|
||
<div className="cloud-page">
|
||
<PageHeading
|
||
eyebrow="OVERVIEW / 总览"
|
||
title={`晚上好,${viewer.displayName}`}
|
||
description="这里显示控制平面真实保存的公测接入状态;原生会话仍由你的主机和 NekoNest PWA 提供。"
|
||
actions={snapshot.connection.state === "ready"
|
||
? <OpenPwaButton />
|
||
: <Link className="button button-primary" href={onboarding.primaryHref}>{onboarding.primaryLabel}</Link>}
|
||
/>
|
||
|
||
<section className="metric-grid" aria-label="账户概览">
|
||
<article className="metric-card accent-card">
|
||
<span>当前权益</span>
|
||
<strong>{entitlementLabel}</strong>
|
||
<small>{entitlementDetail}</small>
|
||
</article>
|
||
<article className="metric-card">
|
||
<span>启用主机</span>
|
||
<strong>{entitlement.activeSlots}</strong>
|
||
<small>{entitlement.reservedSlots ? `${entitlement.reservedSlots} 个配对请求占位中` : "没有等待配对的占位"}</small>
|
||
</article>
|
||
<article className="metric-card">
|
||
<span>租户运行态</span>
|
||
<strong>{connection.label}</strong>
|
||
<small>{snapshot.connection.homeRegion
|
||
? `${snapshot.connection.homeRegion} · generation ${snapshot.connection.placementGeneration ?? "-"}`
|
||
: "等待 home region 分配"}</small>
|
||
</article>
|
||
<article className="metric-card">
|
||
<span>下次付款</span>
|
||
<strong>无</strong>
|
||
<small>公测不绑支付方式,也不会自动续费</small>
|
||
</article>
|
||
</section>
|
||
|
||
<div className="dashboard-columns">
|
||
<section className="panel">
|
||
<div className="panel-heading">
|
||
<div><span>主机</span><h2>接入状态</h2></div>
|
||
<Link href="/dashboard/hosts">查看全部 →</Link>
|
||
</div>
|
||
{activeHosts.length ? (
|
||
<div className="host-list">
|
||
{activeHosts.slice(0, 4).map((host) => {
|
||
const contact = deriveControlPlaneContact(host.control_plane_last_seen_at);
|
||
return (
|
||
<article className="host-row" key={host.id}>
|
||
<span className={`host-icon host-${host.os}`}>{host.os === "windows" ? "W" : "L"}</span>
|
||
<div><strong>{host.name}</strong><small>{host.daemon_version || "daemon 版本待上报"}</small></div>
|
||
<div className="host-state">
|
||
<StatusPill tone={contact.tone}>{contact.label}</StatusPill>
|
||
<small>{host.control_plane_last_seen_at ? formatDate(host.control_plane_last_seen_at, true) : "等待首次 Relay 授权"}</small>
|
||
</div>
|
||
</article>
|
||
);
|
||
})}
|
||
</div>
|
||
) : (
|
||
<div className="empty-state compact-empty">
|
||
<span className="empty-symbol">+</span>
|
||
<h3>还没有已认领的主机</h3>
|
||
<p>先创建一次性引导凭证。daemon 完成控制面认领后,会在同一个稳定服务地址等待共享 Relay 就绪。</p>
|
||
<Link className="button button-secondary" href="/dashboard/hosts/new">创建配对请求</Link>
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
<section className="panel attention-panel">
|
||
<div className="panel-heading"><div><span>当前需要注意</span><h2>诚实状态</h2></div></div>
|
||
<div className="attention-list">
|
||
{onboarding.state !== "ready" && <article><StatusPill tone={onboarding.tone}>{onboarding.state === "request_pending" ? "审核中" : onboarding.state === "request_needed" ? "先申请" : "容量已满"}</StatusPill><div><strong>{onboarding.title}</strong><p>{onboarding.detail} <Link className="text-link" href={onboarding.primaryHref}>{onboarding.primaryLabel} →</Link></p></div></article>}
|
||
<article><StatusPill tone={connection.tone}>{connection.label}</StatusPill><div><strong>{connection.detail}</strong><p>{connection.nextStep}</p></div></article>
|
||
{entitlement.publicBetaState === "gated" && <article><StatusPill tone="warn">公开接入冻结</StatusPill><div><strong>P0 门禁不能被免费政策绕过</strong><p>新公开配对暂不开放;管理员明确签发的免费闭测邀请仍可继续。</p></div></article>}
|
||
<article><StatusPill tone="info">公测</StatusPill><div><strong>报价与订单不开放</strong><p>当前没有任何付款流程;先把主机接入、恢复和稳定性做好。</p></div></article>
|
||
<article><StatusPill tone="info">规则</StatusPill><div><strong>公测结束不自动转付费</strong><p>收费方案以后再定;未主动确认前不会创建付费关系。</p></div></article>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<section className="path-panel">
|
||
<div><span className="eyebrow">CONNECTION PATH</span><h2>按当前状态完成下一步。</h2></div>
|
||
<ol>
|
||
<li className={entitlement.mode !== "none" ? "done" : "current"}><span>01</span><div><strong>获得免费测试资格</strong><p>{onboarding.title}。{onboarding.detail}</p></div></li>
|
||
<li className={snapshot.pairings.length || activeHosts.length ? "done" : onboarding.state === "ready" ? "current" : undefined}><span>02</span><div><strong>创建短时配对请求</strong><p>有可用免费名额后,控制平面生成十分钟一次性码。</p></div></li>
|
||
<li className={activeHosts.length ? "done" : snapshot.pairings.length ? "current" : undefined}><span>03</span><div><strong>daemon 认领并证明主机身份</strong><p>控制面签发独立设备令牌,并推进租户 authorization revision。</p></div></li>
|
||
<li className={snapshot.connection.state === "ready" ? "done" : activeHosts.length ? "current" : undefined}><span>04</span><div><strong>共享 sealed Relay 就绪</strong><p>{snapshot.connection.state === "ready" ? "daemon 保持稳定服务地址即可连接。" : "等待租户 placement 指向健康节点;不会向客户端释放后端节点 URL。"}</p></div></li>
|
||
</ol>
|
||
</section>
|
||
</div>
|
||
</DashboardShell>
|
||
);
|
||
}
|