feat: establish NekoNest Cloud control and relay
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user