first commit

This commit is contained in:
npc0-hue
2026-07-11 14:56:10 +08:00
commit 7e05d0a4e7
660 changed files with 78119 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
import { Sparkles, WandSparkles } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { platformApiClient } from "../api/client";
import type { AuditEventResponse, RunEndpointResponse } from "../api/types";
import { EmptyState, ErrorState, LoadingState } from "../components/StateViews";
import { cx } from "../utils/classes";
type ModuleState<T> = { status: "loading" } | { status: "error"; reason: string } | { status: "ready"; data: T };
export function MaintenancePage() {
const [endpoints, setEndpoints] = useState<ModuleState<RunEndpointResponse[]>>({ status: "loading" });
const [events, setEvents] = useState<ModuleState<AuditEventResponse[]>>({ status: "loading" });
const refreshEndpoints = useCallback(async () => {
setEndpoints({ status: "loading" });
try {
const response = await platformApiClient.listRunEndpoints();
setEndpoints({ status: "ready", data: response.items });
} catch (error) {
setEndpoints({ status: "error", reason: error instanceof Error ? error.message : "加载失败" });
}
}, []);
const refreshEvents = useCallback(async () => {
setEvents({ status: "loading" });
try {
const response = await platformApiClient.listAuditEvents();
setEvents({ status: "ready", data: response.items });
} catch (error) {
setEvents({ status: "error", reason: error instanceof Error ? error.message : "加载失败" });
}
}, []);
useEffect(() => {
void refreshEndpoints();
void refreshEvents();
}, [refreshEndpoints, refreshEvents]);
return (
<div className="maintenance-page">
<header className="page-header">
<div>
<p className="page-kicker"></p>
<h1 className="page-title">
<WandSparkles size={22} style={{ verticalAlign: "-3px" }} />
</h1>
</div>
<button
type="button"
className="icon-command"
onClick={() => {
void refreshEndpoints();
void refreshEvents();
}}
>
<Sparkles size={16} />
<span></span>
</button>
</header>
<section className="console-panel" aria-label="run endpoints">
<div className="panel-header">
<h2></h2>
</div>
{endpoints.status === "loading" && <LoadingState label="正在加载运行节点…" compact />}
{endpoints.status === "error" && (
<ErrorState title="运行节点加载失败" reason={endpoints.reason} diagnosticId="maintenance-endpoints" onRetry={() => void refreshEndpoints()} compact />
)}
{endpoints.status === "ready" && endpoints.data.length === 0 && (
<EmptyState title="暂无运行节点" description="还没有运行端注册到平台。" actionLabel="刷新" onAction={() => void refreshEndpoints()} />
)}
{endpoints.status === "ready" && endpoints.data.length > 0 && (
<div className="resource-list">
{endpoints.data.map((endpoint) => (
<article key={endpoint.id} className="resource-list-item">
<div>
<strong>{endpoint.displayName}</strong>
<span className="provider-id">{endpoint.id}</span>
</div>
<span className={cx("status-pill", endpoint.status === "online" ? "status-active" : endpoint.status === "offline" ? "status-error" : "status-disabled")}>
{endpoint.status === "online" ? "在线" : endpoint.status === "offline" ? "离线" : endpoint.status}
</span>
<span>
{endpoint.capacity.runningJobs}/{endpoint.capacity.maxJobs} {endpoint.capacity.queuedJobs}
</span>
<span> {new Date(endpoint.lastHeartbeatAt).toLocaleString()}</span>
</article>
))}
</div>
)}
</section>
<section className="console-panel" aria-label="audit events">
<div className="panel-header">
<h2></h2>
</div>
{events.status === "loading" && <LoadingState label="正在加载审计事件…" compact />}
{events.status === "error" && (
<ErrorState title="审计事件加载失败" reason={events.reason} diagnosticId="maintenance-audit" onRetry={() => void refreshEvents()} compact />
)}
{events.status === "ready" && events.data.length === 0 && (
<EmptyState title="暂无审计事件" description="平台还没有记录任何审计事件。" actionLabel="刷新" onAction={() => void refreshEvents()} />
)}
{events.status === "ready" && events.data.length > 0 && (
<div className="operation-list">
{events.data.slice(0, 30).map((event) => (
<div key={event.id} className="operation-item">
<div className="operation-item-head">
<strong>{event.summary || `${event.action} ${event.resourceKind}`}</strong>
<span className={cx("status-pill", event.result === "success" ? "status-active" : "status-error")}>{event.result}</span>
</div>
<div className="operation-meta">
<span>
<code>{event.id}</code>
</span>
<span> {event.actorId}</span>
<span>
{event.resourceKind}/{event.resourceId}
</span>
<span>{new Date(event.createdAt).toLocaleString()}</span>
</div>
</div>
))}
</div>
)}
</section>
</div>
);
}