feat: establish NekoNest Cloud control and relay
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { RouteErrorState } from "../components/RouteStates";
|
||||
|
||||
export default function StatusError({ reset }: { reset: () => void }) {
|
||||
return (
|
||||
<RouteErrorState
|
||||
area="服务状态"
|
||||
title="状态页暂时不可用"
|
||||
description="这次没有读取到可信的服务公告,因此不会显示推测的“服务正常”。"
|
||||
reset={reset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { RouteLoadingState } from "../components/RouteStates";
|
||||
|
||||
export default function StatusLoading() {
|
||||
return (
|
||||
<RouteLoadingState
|
||||
area="服务状态"
|
||||
description="正在核对免费公测服务的最新公告和恢复记录。"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { PublicShell, StatusPill, formatDate } from "../components/Shells";
|
||||
import { getServiceStatusSnapshot } from "@/db/repository";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const statusCopy = {
|
||||
operational: {
|
||||
label: "服务正常",
|
||||
tone: "good" as const,
|
||||
summary: "目前没有正在处理的服务故障或维护公告。",
|
||||
},
|
||||
maintenance: {
|
||||
label: "计划维护",
|
||||
tone: "info" as const,
|
||||
summary: "部分能力正在维护,请按公告中的建议操作。",
|
||||
},
|
||||
degraded: {
|
||||
label: "服务降级",
|
||||
tone: "warn" as const,
|
||||
summary: "部分用户可能遇到延迟、重连或接入异常。",
|
||||
},
|
||||
outage: {
|
||||
label: "服务中断",
|
||||
tone: "danger" as const,
|
||||
summary: "当前存在影响使用的服务中断,我们正在处理。",
|
||||
},
|
||||
};
|
||||
|
||||
export default async function ServiceStatusPage() {
|
||||
const snapshot = await getServiceStatusSnapshot();
|
||||
const copy = statusCopy[snapshot.status];
|
||||
const resolved = snapshot.recentIncidents.filter(
|
||||
(incident) => incident.status === "resolved",
|
||||
);
|
||||
|
||||
return (
|
||||
<PublicShell serviceStatus={snapshot}>
|
||||
<div className="public-page status-page">
|
||||
<header className={`status-hero status-hero-${snapshot.status}`}>
|
||||
<StatusPill tone={copy.tone}>{copy.label}</StatusPill>
|
||||
<h1>{copy.summary}</h1>
|
||||
<p>
|
||||
这里发布 NekoNest Cloud 免费公测的维护、降级和中断信息。主机自身离线但这里显示正常时,请先查看控制台接入状态。
|
||||
</p>
|
||||
<small>最近核对:{formatDate(snapshot.checkedAt, true)}</small>
|
||||
</header>
|
||||
|
||||
<section className="status-section">
|
||||
<div className="section-heading compact-heading">
|
||||
<span className="eyebrow">ACTIVE / 处理中</span>
|
||||
<h2>当前事件</h2>
|
||||
</div>
|
||||
{snapshot.activeIncidents.length ? (
|
||||
<div className="incident-list">
|
||||
{snapshot.activeIncidents.map((incident) => (
|
||||
<article className={`incident-card incident-${incident.severity}`} key={incident.id}>
|
||||
<div className="incident-card-heading">
|
||||
<StatusPill tone={statusCopy[incident.severity].tone}>
|
||||
{statusCopy[incident.severity].label}
|
||||
</StatusPill>
|
||||
<span>{formatDate(incident.started_at, true)}</span>
|
||||
</div>
|
||||
<h3>{incident.title}</h3>
|
||||
<p>{incident.message}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="status-empty">
|
||||
<span aria-hidden="true">✓</span>
|
||||
<div><strong>没有正在处理的事件</strong><p>如果你仍然无法连接主机,请在控制台提交问题反馈。</p></div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="status-section status-history">
|
||||
<div className="section-heading compact-heading">
|
||||
<span className="eyebrow">HISTORY / 最近恢复</span>
|
||||
<h2>事件记录</h2>
|
||||
</div>
|
||||
{resolved.length ? (
|
||||
<div className="incident-history-list">
|
||||
{resolved.map((incident) => (
|
||||
<article key={incident.id}>
|
||||
<div><strong>{incident.title}</strong><span>{formatDate(incident.resolved_at, true)}</span></div>
|
||||
<p>{incident.resolution}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="status-history-empty">还没有已恢复的公开事件。</p>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</PublicShell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user