style(copy): punctuation rules and site-wide fix with a CI check (QA U11)
- glossary: 10 punctuation rules (full-width in Chinese, CJK/Latin spacing, full-width parens around Chinese, single ellipsis, 「」 quotes, period rules) - scripts/check-copy-punctuation.mjs: checks string literals, templates and JSX text (not comments, types, keys, match/compare strings); --fix, --strict, --by-file; wired into deno task ci as check:copy in warn mode - --fix applied to web, server and contracts; files other lanes were editing (subscriptions, sites, browser, agent, auth/account, inventory) left for them - tests updated for the changed messages Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
9b017906cf
commit
52402864d6
143 files changed
+985
-606
No files matched your search
@@ -82,7 +82,7 @@ const ACTION_OPTIONS = [
|
||||
"downloader_instance",
|
||||
"media_server_instance",
|
||||
"path_mapping",
|
||||
].map((p) => ({ value: `${p}.*`, label: `${p}.*(全部)` })),
|
||||
].map((p) => ({ value: `${p}.*`, label: `${p}.*(全部)` })),
|
||||
...AuditAction.options.map((a) => ({ value: a, label: `${actionLabel(a)} · ${a}` })),
|
||||
];
|
||||
|
||||
@@ -134,7 +134,7 @@ export default function AuditPage() {
|
||||
<>
|
||||
<PageHeader title="审计" description="登录、设置、密钥与实例变更,新的在前。不记录凭证内容。" />
|
||||
<FilterBar
|
||||
search={{ value: target, onChange: setTarget, placeholder: "对象类型,如 downloader_instance" }}
|
||||
search={{ value: target, onChange: setTarget, placeholder: "对象类型,如 downloader_instance" }}
|
||||
onReset={
|
||||
filtered
|
||||
? () => {
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function DownloadPlansPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="下载面板" description="候选被选中后生成计划,同时预留版本目标、空间与预算。" />
|
||||
<PageHeader title="下载面板" description="候选被选中后生成计划,同时预留版本目标、空间与预算。" />
|
||||
<DownloadsTabs />
|
||||
<FilterBar onReset={f.active ? f.reset : undefined}>
|
||||
<FilterSelect
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function DownloadsPage() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="下载面板"
|
||||
description="阈值暂停不能手动绕过;无进展只记录,不自动换源。"
|
||||
description="阈值暂停不能手动绕过;无进展只记录,不自动换源。"
|
||||
actions={
|
||||
<Button variant="outline" size="sm" onClick={() => void qc.invalidateQueries({ queryKey: ["downloads"] })}>
|
||||
<RefreshCw className={downloads.isFetching ? "animate-spin" : undefined} />
|
||||
|
||||
@@ -97,7 +97,7 @@ import { cn, formatBytes } from "~/lib/utils.ts";
|
||||
|
||||
const HEALTH_REASON: Record<string, string> = {
|
||||
root_missing: "挂载点不存在",
|
||||
sentinel_missing: "目录里没有哨兵文件,挂载可能没生效",
|
||||
sentinel_missing: "目录里没有哨兵文件,挂载可能没生效",
|
||||
timeout: "探测超时",
|
||||
permission_denied: "没有读取权限",
|
||||
};
|
||||
@@ -365,7 +365,7 @@ export default function FilesPage() {
|
||||
setQInput("");
|
||||
};
|
||||
const open = (e: FileEntry) => {
|
||||
if (e.escape) return toast.error("链接指向登记目录之外,不能打开");
|
||||
if (e.escape) return toast.error("链接指向登记目录之外,不能打开");
|
||||
if (e.type === "dir") go({ root: rootId, path: e.path });
|
||||
else patch({ sel: e.path, idf: null });
|
||||
};
|
||||
@@ -397,7 +397,7 @@ export default function FilesPage() {
|
||||
{
|
||||
onSuccess: (r) => {
|
||||
const ok = r.items.filter((i) => i.status === "ok").length;
|
||||
toast.success(`探测完成:${ok}/${r.items.length} 个读到媒体信息`, {
|
||||
toast.success(`探测完成:${ok}/${r.items.length} 个读到媒体信息`, {
|
||||
description: r.items.some((i) => i.savedToAsset) ? "媒体信息已写入资产" : "未登记文件的结果缓存 24 小时",
|
||||
});
|
||||
},
|
||||
@@ -598,7 +598,7 @@ export default function FilesPage() {
|
||||
<p className="mt-1.5 max-w-md text-caption text-muted-foreground">
|
||||
{offline ? filesErrorText(list.error) : healthReasonText(root.healthReason)}。离线期间资产状态不变。
|
||||
</p>
|
||||
<p className="mt-1 text-meta text-muted-foreground">上次检查:{relTime(root.checkedAt)}</p>
|
||||
<p className="mt-1 text-meta text-muted-foreground">上次检查:{relTime(root.checkedAt)}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -629,7 +629,7 @@ export default function FilesPage() {
|
||||
plain
|
||||
icon={SearchX}
|
||||
title="没有符合筛选的项"
|
||||
description={q ? `没有匹配“${q}”的项。支持子串和 glob,如 *.mkv` : undefined}
|
||||
description={q ? `没有匹配「${q}」的项。支持子串和 glob,如 *.mkv` : undefined}
|
||||
action={
|
||||
<Button variant="outline" size="sm" onClick={clearFilters}>
|
||||
<X />
|
||||
@@ -682,7 +682,7 @@ export default function FilesPage() {
|
||||
const blockedTip = (op: Parameters<typeof opDisabledReason>[0]) => {
|
||||
if (!root) return null;
|
||||
const bad = selected.map((e) => opDisabledReason(op, e, root)).filter(Boolean) as string[];
|
||||
return bad.length ? `${bad[0]}(${bad.length} 项)` : null;
|
||||
return bad.length ? `${bad[0]}(${bad.length} 项)` : null;
|
||||
};
|
||||
const moreItems = (): MenuItem[] => {
|
||||
const items: MenuItem[] = [];
|
||||
@@ -706,7 +706,7 @@ export default function FilesPage() {
|
||||
.filter((e) => opApplies("identify", e))
|
||||
.map((e) => (root ? opDisabledReason("organize", e, root) : null))
|
||||
.filter(Boolean) as string[];
|
||||
return bad.length ? `${bad[0]}(${bad.length} 项)` : null;
|
||||
return bad.length ? `${bad[0]}(${bad.length} 项)` : null;
|
||||
})();
|
||||
const moveTip = blockedTip("move");
|
||||
const deleteTip = blockedTip("delete");
|
||||
@@ -796,7 +796,7 @@ export default function FilesPage() {
|
||||
}
|
||||
if (e.key === "ArrowDown") gridRef.current?.focus();
|
||||
}}
|
||||
placeholder="名称或 glob,如 *.mkv"
|
||||
placeholder="名称或 glob,如 *.mkv"
|
||||
aria-label="搜索当前目录"
|
||||
className="h-8 pl-9"
|
||||
/>
|
||||
@@ -819,7 +819,7 @@ export default function FilesPage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={order === "asc" ? "升序,点此改为降序" : "降序,点此改为升序"}
|
||||
aria-label={order === "asc" ? "升序,点此改为降序" : "降序,点此改为升序"}
|
||||
title={order === "asc" ? "升序" : "降序"}
|
||||
onClick={() => patch({ order: order === "asc" ? "desc" : null })}
|
||||
>
|
||||
@@ -861,7 +861,7 @@ export default function FilesPage() {
|
||||
key: "mkdir",
|
||||
label: "新建目录",
|
||||
icon: FolderPlus,
|
||||
disabled: root?.writable ? null : root?.kind === "download" ? "下载目录里不能新建目录" : "只读目录,不能新建",
|
||||
disabled: root?.writable ? null : root?.kind === "download" ? "下载目录里不能新建目录" : "只读目录,不能新建",
|
||||
onSelect: () => setDialog({ kind: "mkdir" }),
|
||||
},
|
||||
{
|
||||
@@ -1046,7 +1046,7 @@ export default function FilesPage() {
|
||||
? undefined
|
||||
: root?.kind === "download"
|
||||
? "下载目录里不能新建目录"
|
||||
: "只读目录,不能新建"
|
||||
: "只读目录,不能新建"
|
||||
}
|
||||
>
|
||||
<Button
|
||||
@@ -1070,7 +1070,7 @@ export default function FilesPage() {
|
||||
|
||||
{first?.truncated ? (
|
||||
<div role="status" className="border-b border-warning/30 bg-warning/10 px-3 py-1.5 text-meta text-warning">
|
||||
目录过大,只读了前 {first.total.toLocaleString("zh-CN")} 项。用搜索或进入子目录。
|
||||
目录过大,只读了前 {first.total.toLocaleString("zh-CN")} 项。用搜索或进入子目录。
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -163,10 +163,10 @@ function AutomationBanner({ a }: { a: Dashboard["automation"] }) {
|
||||
</div>
|
||||
<p className="mt-0.5 text-caption text-muted-foreground">
|
||||
{paused
|
||||
? "自动收录、下载、整理已停止,手动操作仍可用。"
|
||||
? "自动收录、下载、整理已停止,手动操作仍可用。"
|
||||
: readOnly
|
||||
? "只核对,不写入。确认无误后关闭。"
|
||||
: `${a.activeSubscriptions} 个订阅在追踪,自动收录、下载和整理。`}
|
||||
? "只核对,不写入。确认无误后关闭。"
|
||||
: `${a.activeSubscriptions} 个订阅在追踪,自动收录、下载和整理。`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -201,7 +201,7 @@ function AutomationBanner({ a }: { a: Dashboard["automation"] }) {
|
||||
</Button>
|
||||
}
|
||||
title="暂停全部自动化?"
|
||||
description="自动收录、下载、整理会停止,已提交的下载任务不受影响。可随时恢复。"
|
||||
description="自动收录、下载、整理会停止,已提交的下载任务不受影响。可随时恢复。"
|
||||
confirmLabel="暂停自动化"
|
||||
onConfirm={() => setAutomation({ enabled: false }, "自动化已暂停")}
|
||||
/>
|
||||
|
||||
@@ -141,7 +141,7 @@ function OptionButton({ d, option }: { d: Decision; option: DecisionOption }) {
|
||||
description={
|
||||
<>
|
||||
{option.description ? `${option.description}。` : ""}
|
||||
{option.capabilityId ? `由能力 ${option.capabilityId} 执行,仍做授权校验。` : null}
|
||||
{option.capabilityId ? `由能力 ${option.capabilityId} 执行,仍做授权校验。` : null}
|
||||
</>
|
||||
}
|
||||
confirmLabel={option.label}
|
||||
@@ -155,7 +155,7 @@ function OptionButton({ d, option }: { d: Decision; option: DecisionOption }) {
|
||||
...(identity ? { identity } : {}),
|
||||
})
|
||||
.then(
|
||||
() => toast.success(`已处理:${option.label}`),
|
||||
() => toast.success(`已处理:${option.label}`),
|
||||
(err) => {
|
||||
toast.error(errorMessage(err, "处理失败"));
|
||||
throw err;
|
||||
@@ -176,7 +176,7 @@ function OptionButton({ d, option }: { d: Decision; option: DecisionOption }) {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormField>
|
||||
<FormField label="TMDB ID" htmlFor={`${uid}-tmdb`} required hint="纯数字,如 872585">
|
||||
<FormField label="TMDB ID" htmlFor={`${uid}-tmdb`} required hint="纯数字,如 872585">
|
||||
<Input
|
||||
id={`${uid}-tmdb`}
|
||||
inputMode="numeric"
|
||||
@@ -191,7 +191,7 @@ function OptionButton({ d, option }: { d: Decision; option: DecisionOption }) {
|
||||
<EntityPicker value={entity} onChange={setEntity} />
|
||||
</FormField>
|
||||
) : null}
|
||||
<FormField label="备注" htmlFor={`${uid}-note`} hint="可选,记在处理结果里">
|
||||
<FormField label="备注" htmlFor={`${uid}-note`} hint="可选,记在处理结果里">
|
||||
<Textarea id={`${uid}-note`} value={note} onChange={(e) => setNote(e.target.value)} rows={2} />
|
||||
</FormField>
|
||||
</ConfirmDialog>
|
||||
@@ -208,8 +208,8 @@ function DismissButton({ d }: { d: Decision }) {
|
||||
忽略
|
||||
</Button>
|
||||
}
|
||||
title="忽略这条事项?"
|
||||
description="只关闭提醒,问题本身不变;同一根因出现新证据时会重新提出。"
|
||||
title="忽略这条事项?"
|
||||
description="只关闭提醒,问题本身不变;同一根因出现新证据时会重新提出。"
|
||||
confirmLabel="忽略"
|
||||
onConfirm={() =>
|
||||
resolve.mutateAsync({ id: d.id, dismiss: true }).then(
|
||||
@@ -343,7 +343,7 @@ export default function InboxPage() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="待处理"
|
||||
description="Agent 调查和重试后仍未解决的事项,按作品和问题类型合并"
|
||||
description="Agent 调查和重试后仍未解决的事项,按作品和问题类型合并"
|
||||
actions={
|
||||
<>
|
||||
<span className="hidden items-center gap-1.5 text-meta text-muted-foreground sm:inline-flex">
|
||||
@@ -379,7 +379,7 @@ export default function InboxPage() {
|
||||
key={k}
|
||||
label={DECISION_KIND[k]}
|
||||
value={v.count}
|
||||
hint={`最高级别:${SEVERITY[v.sev].label}`}
|
||||
hint={`最高级别:${SEVERITY[v.sev].label}`}
|
||||
tone={SEVERITY[v.sev].tone}
|
||||
active={kind === k}
|
||||
onClick={() => set("kind", kind === k ? "" : k)}
|
||||
|
||||
@@ -101,7 +101,7 @@ function PathsPanel({ detail }: { detail: LibraryDetail }) {
|
||||
plain
|
||||
icon={FolderTree}
|
||||
title="还没有入库文件"
|
||||
description="版本目标满足后,文件位置显示在这里。"
|
||||
description="版本目标满足后,文件位置显示在这里。"
|
||||
className="py-8"
|
||||
/>
|
||||
) : (
|
||||
@@ -173,7 +173,7 @@ function IdentityPanel({ detail }: { detail: LibraryDetail }) {
|
||||
return (
|
||||
<Panel
|
||||
title="外部身份"
|
||||
description="以本地 ID 为准,外部身份可修改"
|
||||
description="以本地 ID 为准,外部身份可修改"
|
||||
actions={
|
||||
<Button variant="outline" size="sm" onClick={() => setOpen(true)}>
|
||||
<PencilLine />
|
||||
@@ -287,7 +287,7 @@ function MetadataPanel({ detail, entity }: { detail: LibraryDetail; entity: Medi
|
||||
) : null}
|
||||
{cands.length ? (
|
||||
<p className="mt-0.5 text-meta break-words text-muted-foreground">
|
||||
候选:
|
||||
候选:
|
||||
{cands.map((c) => `${METADATA_SOURCE[c.source] ?? c.source}「${String(c.value)}」`).join(" / ")}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -305,8 +305,8 @@ function MetadataPanel({ detail, entity }: { detail: LibraryDetail; entity: Medi
|
||||
<LockOpen />
|
||||
</Button>
|
||||
}
|
||||
title={`解锁「${ENTITY_FIELD_LABEL[field]}」?`}
|
||||
description="取消人工修改,下次刷新按来源优先级取值。"
|
||||
title={`解锁「${ENTITY_FIELD_LABEL[field]}」?`}
|
||||
description="取消人工修改,下次刷新按来源优先级取值。"
|
||||
confirmLabel="解锁"
|
||||
onConfirm={() =>
|
||||
unlock.mutateAsync(field).then(
|
||||
@@ -335,7 +335,7 @@ function MetadataPanel({ detail, entity }: { detail: LibraryDetail; entity: Medi
|
||||
</ul>
|
||||
{entity?.aliases.length ? (
|
||||
<div className="mt-3 border-t border-border/60 pt-3">
|
||||
<p className="mb-1.5 text-meta text-muted-foreground">别名(用于模糊召回)</p>
|
||||
<p className="mb-1.5 text-meta text-muted-foreground">别名(用于模糊召回)</p>
|
||||
<ChipList
|
||||
items={entity.aliases.map((a) => (
|
||||
<span key={a.id}>
|
||||
@@ -360,7 +360,7 @@ function SlotsPanel({ detail, units }: { detail: LibraryDetail; units: ContentUn
|
||||
plain
|
||||
icon={CalendarClock}
|
||||
title="还没有版本目标"
|
||||
description="新建订阅或版本目标后,按目标补齐和升级。"
|
||||
description="新建订阅或版本目标后,按目标补齐和升级。"
|
||||
action={
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link to={consolePath("subscriptions")}>去订阅</Link>
|
||||
@@ -456,7 +456,7 @@ function UnitsPanel({ entity, detail }: { entity: ReturnType<typeof useEntityDet
|
||||
a.segment.localeCompare(b.segment),
|
||||
);
|
||||
return (
|
||||
<Panel title="内容单元" description="临时单元按解析结果创建,确认或改编号后锁定" bodyClassName="p-0 sm:p-0">
|
||||
<Panel title="内容单元" description="临时单元按解析结果创建,确认或改编号后锁定" bodyClassName="p-0 sm:p-0">
|
||||
<DataTable
|
||||
plain
|
||||
columns={columns}
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function LibraryPage() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="媒体概览"
|
||||
description="本地账本:作品、版本目标、资产与外部身份。自动匹配不覆盖人工锁定的外部身份。"
|
||||
description="本地账本:作品、版本目标、资产与外部身份。自动匹配不覆盖人工锁定的外部身份。"
|
||||
actions={
|
||||
view === "unidentified" ? (
|
||||
<Button asChild variant="outline" size="sm">
|
||||
@@ -208,7 +208,7 @@ function WorksView() {
|
||||
? { title: "没有符合条件的作品", description: "换个关键字或清除筛选。", icon: LibraryIcon }
|
||||
: {
|
||||
title: "还没有作品",
|
||||
description: "扫描库根或新建订阅后,作品会出现在这里。",
|
||||
description: "扫描库根或新建订阅后,作品会出现在这里。",
|
||||
icon: LibraryIcon,
|
||||
action: (
|
||||
<Button asChild variant="outline" size="sm">
|
||||
|
||||
@@ -107,7 +107,7 @@ function FileRow({ f }: { f: PublicOrganizationFile }) {
|
||||
</div>
|
||||
{reason ? (
|
||||
<p className={cn("text-caption break-words", f.failure ? "text-destructive" : "text-muted-foreground")}>
|
||||
{f.failure ? "失败" : "跳过"}原因:{reasonText(reason)}
|
||||
{f.failure ? "失败" : "跳过"}原因:{reasonText(reason)}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="text-meta text-muted-foreground">
|
||||
@@ -153,10 +153,10 @@ export default function OrganizeRecordPage() {
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
openAgentDrawer({
|
||||
label: `整理记录:${r.media?.title ?? r.id.slice(0, 8)}`,
|
||||
label: `整理记录:${r.media?.title ?? r.id.slice(0, 8)}`,
|
||||
recordId: r.id,
|
||||
entityId: r.media?.entityId,
|
||||
goal: "排查这次整理为什么没有全部成功,给出恢复办法",
|
||||
goal: "排查这次整理为什么没有全部成功,给出恢复办法",
|
||||
})
|
||||
}
|
||||
>
|
||||
@@ -180,7 +180,7 @@ export default function OrganizeRecordPage() {
|
||||
<StatusBadge>{ACTION_LABEL[r.action]}</StatusBadge>
|
||||
<StatusBadge>
|
||||
{TRIGGER_LABEL[r.trigger]}
|
||||
{r.trigger === "agent" ? "(计为自动)" : ""}
|
||||
{r.trigger === "agent" ? "(计为自动)" : ""}
|
||||
</StatusBadge>
|
||||
<span className="text-meta text-muted-foreground tabular-nums">
|
||||
{r.counts.succeeded}/{r.counts.total} 成功 · 失败 {r.counts.failed} · 跳过 {r.counts.skipped}
|
||||
@@ -243,7 +243,7 @@ export default function OrganizeRecordPage() {
|
||||
</ul>
|
||||
) : (
|
||||
<p className="px-4 pb-4 text-caption text-muted-foreground">
|
||||
{r.status === "queued" ? "排队中,还没有文件明细。" : "没有文件级结果。"}
|
||||
{r.status === "queued" ? "排队中,还没有文件明细。" : "没有文件级结果。"}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function OrganizePage() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="整理记录"
|
||||
description="每次整理的前后路径与逐文件结果,自动和手动整理都记在这里。"
|
||||
description="每次整理的前后路径与逐文件结果,自动和手动整理都记在这里。"
|
||||
actions={
|
||||
<Button size="sm" onClick={() => navigate("/console/files")}>
|
||||
<FolderInput />
|
||||
@@ -180,7 +180,7 @@ export default function OrganizePage() {
|
||||
empty={{
|
||||
icon: FileStack,
|
||||
title: filtered ? "没有符合筛选的整理记录" : "还没有整理记录",
|
||||
description: "下载完成后会自动整理,也可以在文件管理里选中文件整理。",
|
||||
description: "下载完成后会自动整理,也可以在文件管理里选中文件整理。",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -76,7 +76,7 @@ function problems(d: SystemHealth): string[] {
|
||||
const out: string[] = [];
|
||||
const stale = d.processes.filter((p) => p.stale);
|
||||
if (stale.length) out.push(`${stale.length} 个进程超过 45 秒没有心跳`);
|
||||
if (!d.processes.some((p) => !p.stale && p.processKind !== "api")) out.push("没有在线的 worker,任务不会被执行");
|
||||
if (!d.processes.some((p) => !p.stale && p.processKind !== "api")) out.push("没有在线的 worker,任务不会被执行");
|
||||
const lag = d.processes.filter((p) => !p.stale && p.eventLoopLagMaxMs >= 1000);
|
||||
if (lag.length) out.push(`${lag.length} 个进程事件循环延迟超过 1 秒`);
|
||||
if (d.db.level === "warn" || d.db.level === "critical") out.push("数据库延迟或连接数偏高");
|
||||
@@ -116,7 +116,7 @@ function Banner({ d }: { d: SystemHealth }) {
|
||||
<p className="font-medium text-foreground">
|
||||
{ok ? "全部正常" : lv.label}
|
||||
<span className="ml-2 font-normal text-muted-foreground">
|
||||
检查于 {formatRelative(d.checkedAt)} · API {d.api.version},已运行 {formatDuration(d.api.uptimeSec)}
|
||||
检查于 {formatRelative(d.checkedAt)} · API {d.api.version},已运行 {formatDuration(d.api.uptimeSec)}
|
||||
</span>
|
||||
</p>
|
||||
{list.length ? (
|
||||
@@ -262,7 +262,7 @@ function History() {
|
||||
<ErrorState plain error={h.error} onRetry={() => h.refetch()} />
|
||||
) : (
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<DetailSection title="事件循环延迟峰值(毫秒)">
|
||||
<DetailSection title="事件循环延迟峰值(毫秒)">
|
||||
<HistoryChart data={h.data} metric="lagMaxMs" unit="毫秒" hours={hours} />
|
||||
</DetailSection>
|
||||
<DetailSection title="SSE 连接数">
|
||||
@@ -280,7 +280,7 @@ function Disks({ d }: { d: SystemHealth }) {
|
||||
return (
|
||||
<Card title="磁盘" icon={HardDrive} level={worst(d.disks.map((x) => x.level))}>
|
||||
<p className="mb-3 text-meta text-muted-foreground">
|
||||
阈值:{t.warnPercent}% 需要关注,{t.criticalPercent}% 需要处理(取自缓存设置)
|
||||
阈值:{t.warnPercent}% 需要关注,{t.criticalPercent}% 需要处理(取自缓存设置)
|
||||
</p>
|
||||
<ul className="space-y-3">
|
||||
{d.disks.map((x) => {
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function TasksPage() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="任务"
|
||||
description="每次运行都是一条任务,不论定时、事件、手动还是 Agent 触发。"
|
||||
description="每次运行都是一条任务,不论定时、事件、手动还是 Agent 触发。"
|
||||
actions={
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function NotFound() {
|
||||
<div className="grid min-h-screen place-items-center bg-background px-4">
|
||||
<EmptyState
|
||||
title="页面不存在"
|
||||
description="检查地址,或回到首页。"
|
||||
description="检查地址,或回到首页。"
|
||||
action={
|
||||
<Link to="/console" className={buttonVariants({ variant: "outline" })}>
|
||||
回到首页
|
||||
|
||||
Reference in new issue
Block a user