feat: redesign responsive console and fix restore regressions

This commit is contained in:
2026-10-15 01:08:55 +08:00
parent 3bf197206a
commit 56c8f76e32
49 changed files with 3455 additions and 447 deletions
+27 -8
View File
@@ -1,11 +1,30 @@
import { api } from './api';
import { Panel, ResourceState, useAction, useResource } from './ui';
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();
return <Panel title="孤儿容器回收" action={<button onClick={list.refresh}>核验并刷新</button>}>
<p>仅列出已停止、归属和挂载核验通过且无实例或升级引用的容器。回收保留所有挂载数据,不删除网络。运行中及来源不明的容器不会列入。</p>
{action.feedback}<ResourceState resource={list}/>
{list.data?.length===0 && <p>没有可安全回收的容器。</p>}
{list.data?.map(id=><div key={id}><code>{id}</code><button disabled={action.busy} onClick={()=>action.run(async()=>{if(window.prompt('请输入完整容器 ID 确认回收(数据保留)')!==id)throw new Error('确认不匹配,未执行');await api.request('/orphans?id='+encodeURIComponent(id)+'&confirm='+encodeURIComponent(id),'DELETE');await list.refresh();},'容器已回收,数据已保留','确认回收此停止的孤儿容器?')}>回收</button></div>)}
</Panel>;
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>;
}