31 lines
2.0 KiB
TypeScript
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>;
|
|
}
|