Files
nekonest-cloud/app/status/page.tsx
T

98 lines
3.6 KiB
TypeScript

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>
);
}