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 -10
View File
@@ -1,12 +1,16 @@
import { useEffect, useRef, useState, type FormEvent } from 'react';
import { api, selectableImage, type Instance } from './api';
import { ResourceState, useAction, useResource } from './ui';
import { AlertBox } from './components/ui/alert';
import { buttonVariants } from './components/ui/button';
import { inputClass, selectClass, selectStyle } from './components/ui/input';
import { cn } from './lib/utils';
export function CreateInstance({close, refresh}: {close: () => void; refresh: () => Promise<void>}) {
const images = useResource(api.images), action = useAction();
const [progress, setProgress] = useState('');
const active = useRef(true);
useEffect(() => {active.current = true; return () => {active.current = false;};}, []);
useEffect(() => { active.current = true; return () => { active.current = false; }; }, []);
const available = images.data?.filter(selectableImage) || [];
async function submit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
@@ -16,18 +20,31 @@ export function CreateInstance({close, refresh}: {close: () => void; refresh: ()
if (!active.current) return;
if (!image) throw new Error('请选择可用的本地镜像。');
setProgress('正在创建实例,请勿重复提交…');
const created = await api.request<Instance>('/instances', 'POST', {name, image_id:image.image_id || image.id});
const created = await api.request<Instance>('/instances', 'POST', {name, image_id: image.image_id || image.id});
if (!created?.id) throw new Error('未收到实例确认,请刷新实例列表核实,勿重复提交。');
if (active.current) {await refresh(); close();}
if (active.current) { await refresh(); close(); }
});
if (active.current) setProgress('');
}
return <form className="inline-form" onSubmit={submit}><h3>创建独立实例</h3>{action.feedback}
<p className="callout">请选择已验证并准入的本地镜像。创建使用所选镜像的精确 ID,不会自动构建或切换版本。</p>
<fieldset disabled={action.busy}><label>实例名称<input name="name" required maxLength={64}/></label>
<ResourceState resource={images}/><label>本地镜像<select name="image" required defaultValue=""><option value="" disabled>选择已验证镜像</option>{available.map(i => <option key={i.image_id || i.id} value={i.image_id || i.id}>{i.version} · {i.tag || i.image_id || i.id}</option>)}</select></label>
{!images.loading && !available.length && <p>暂无已准入的可用镜像。请联系管理员构建并验证发布镜像。</p>}
<button className="primary" disabled={!available.length}>确认创建</button>
</fieldset>{progress && <p role="status">{progress}</p>}<button type="button" disabled={action.busy} onClick={close}>关闭</button>
return <form className="mb-5 grid gap-4 rounded-lg border border-border bg-muted/50 p-4" onSubmit={submit}>
<h3 className="m-0 text-[14px] font-semibold">创建独立实例</h3>
{action.feedback}
<AlertBox variant="info">请选择已验证并准入的本地镜像。创建使用所选镜像的精确 ID,不会自动构建或切换版本。</AlertBox>
<fieldset disabled={action.busy} className="m-0 grid min-w-0 gap-4 border-0 p-0">
<label className="grid gap-1.5 text-[13px] font-medium">实例名称<input className={inputClass} name="name" required maxLength={64} /></label>
<ResourceState resource={images} />
<label className="grid gap-1.5 text-[13px] font-medium">本地镜像
<select className={selectClass} style={selectStyle} name="image" required defaultValue="">
<option value="" disabled>选择已验证镜像</option>
{available.map(i => <option key={i.image_id || i.id} value={i.image_id || i.id}>{i.version} · {i.tag || i.image_id || i.id}</option>)}
</select>
</label>
{!images.loading && !available.length && <p className="m-0 text-[13px] text-muted-foreground">暂无已准入的可用镜像。请联系管理员构建并验证发布镜像。</p>}
<div className="flex flex-wrap gap-2">
<button className={cn(buttonVariants())} disabled={!available.length}>确认创建</button>
<button type="button" className={cn(buttonVariants({variant: 'ghost'}))} disabled={action.busy} onClick={close}>关闭</button>
</div>
</fieldset>
{progress && <p role="status" className="m-0 text-[13px] text-muted-foreground">{progress}</p>}
</form>;
}