Initial commit: DSH Web Manager

This commit is contained in:
2026-10-13 09:34:47 +08:00
commit 85332bbc56
135 changed files with 19712 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
import { useEffect, useRef, useState, type FormEvent } from 'react';
import { api, type Image, type Instance } from './api';
import { ResourceState, useAction, useResource } from './ui';
export function CreateInstance({close, refresh}: {close: () => void; refresh: () => Promise<void>}) {
const images = useResource(api.images), action = useAction();
const [mode, setMode] = useState('local'), [progress, setProgress] = useState('');
const [version, setVersion] = useState('');
const [intents, setIntents] = useState<{id:string; name:string; version:string; status:string; error:string}[]>([]);
useEffect(() => {let live=true; const load=()=>api.request<typeof intents>('/intents').then(v=>{if(live)setIntents(v);}).catch(e=>{if(live)setProgress(String(e));}); void load(); const timer=window.setInterval(load,2000); return ()=>{live=false;window.clearInterval(timer);};}, []);
const active = useRef(true);
useEffect(() => {active.current = true; return () => {active.current = false;};}, []);
const available = images.data?.filter(i => !i.status || i.status === 'available') || [];
async function submit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = new FormData(e.currentTarget), name = String(form.get('name'));
await action.run(async () => {
let image: Image | undefined = available.find(i => (i.image_id || i.id) === form.get('image'));
if (mode === 'latest') {
if (!version) {
const latest = await api.request<{version:string}>('/latest');
if (!latest.version) throw new Error('服务器未返回精确版本。');
setVersion(latest.version); return;
}
const intent = await api.request<{id:string}>('/intents', 'POST', {name, version});
if (!intent.id) throw new Error('未收到意图确认,请刷新核实,勿重复提交。');
setIntents(await api.request<typeof intents>('/intents'));
await refresh(); return;
}
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, version:image.version});
if (!created?.id) throw new Error('未收到实例确认,请刷新实例列表核实,勿重复提交。');
if (active.current) {await refresh(); close();}
});
if (active.current) setProgress('');
}
return <form className="inline-form" onSubmit={submit}><h3>创建独立实例</h3>{action.feedback}<p className="callout">确认后服务器持久预留一个名额(每用户最多两个)。刷新或关闭不会取消自动创建;失败释放名额,不回退其他版本。取消等待不会取消共享构建,创建开始后不可撤回。</p><fieldset disabled={action.busy}><label>实例名称<input name="name" required maxLength={64}/></label><label>创建方式<select value={mode} onChange={e => {setMode(e.target.value); setVersion('');}}><option value="local">选择本地镜像</option><option value="latest">构建最新版并创建</option></select></label>{mode === 'local' && <><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>}</>}{mode === 'latest' && version && <p>将锁定精确版本 <strong>{version}</strong>,确认后预留名额并构建创建。</p>}<button className="primary" disabled={mode === 'local' && !available.length}>{mode === 'local' ? '确认创建' : version ? '确认版本并预留创建' : '查询最新版'}</button></fieldset>{progress && <p role="status">{progress}</p>}<ul>{intents.map(i => <li key={i.id}>{i.name} · {i.version} · {i.status} {i.error}{i.status === 'waiting' && <button type="button" disabled={action.busy} onClick={() => void action.run(async () => {await api.request('/intents/' + i.id, 'DELETE'); setIntents(await api.request<typeof intents>('/intents')); await refresh();})}>取消等待并释放名额</button>}{i.status === 'success' && <button type="button" onClick={() => void refresh()}>刷新实例列表</button>}</li>)}</ul><button type="button" onClick={close}>关闭(保留已提交意图)</button></form>;
}