Files
dsh-web/web/src/Orphans.tsx
T

31 lines
2.0 KiB
TypeScript

import { api } from './api';
import { Panel, ResourceState, useAction, useResource, RefreshButton } from './ui';
import { useConfirm } from './components/ConfirmDialog';
import { buttonVariants } from './components/ui/button';
import { cn } from './lib/utils';
export function Orphans() {
const list = useResource(() => api.request<string[]>('/orphans')), action = useAction();
const confirm = useConfirm();
return <Panel title="孤儿容器回收" action={<RefreshButton loading={list.loading} onClick={list.refresh} label="核验并刷新" />}>
{confirm.element}
<p className="mb-4 mt-0 text-[13px] text-muted-foreground">仅列出已停止、归属和挂载核验通过且无实例或升级引用的容器。回收保留所有挂载数据,不删除网络。运行中及来源不明的容器不会列入。</p>
{action.feedback}
<ResourceState resource={list} />
{list.data?.length === 0 && <p className="m-0 text-[13px] text-muted-foreground">没有可安全回收的容器。</p>}
<div className="grid gap-2">
{list.data?.map(id => <div key={id} className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border bg-muted/40 px-3 py-2.5">
<code className="wrap-anywhere">{id}</code>
<button className={cn(buttonVariants({variant: 'destructive-ghost', size: 'sm'}))} disabled={action.busy} onClick={async () => {
if (!await confirm.ask({
title: '回收孤儿容器', danger: true, confirmLabel: '确认回收',
body: '回收保留挂载数据、不删除网络,且不可撤销。请在下方输入完整容器 ID 确认。',
requireText: id, requireTextLabel: '完整容器 ID',
})) return;
void action.run(async () => { await api.request('/orphans?id=' + encodeURIComponent(id) + '&confirm=' + encodeURIComponent(id), 'DELETE'); await list.refresh(); }, '容器已回收,数据已保留');
}}>回收</button>
</div>)}
</div>
</Panel>;
}