refactor!: separate runtime releases from panel source
This commit is contained in:
+12
-24
@@ -1,15 +1,10 @@
|
||||
import { useEffect, useRef, useState, type FormEvent } from 'react';
|
||||
import { api, normalBuildAllowed, selectableImage, type Image, type Instance } from './api';
|
||||
import { message, ResourceState, useAction, useResource } from './ui';
|
||||
import { api, selectableImage, type Instance } from './api';
|
||||
import { ResourceState, useAction, useResource } from './ui';
|
||||
|
||||
export function CreateInstance({close, refresh}: {close: () => void; refresh: () => Promise<void>}) {
|
||||
const compatibility = useResource(api.compatibility);
|
||||
const images = useResource(api.images), action = useAction();
|
||||
const [mode, setMode] = useState('local'), [progress, setProgress] = useState('');
|
||||
const [version, setVersion] = useState('');
|
||||
const [pollError, setPollError] = 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);setPollError('');}}).catch(e=>{if(live)setPollError(message(e));}); void load(); const timer=window.setInterval(load,2000); return ()=>{live=false;window.clearInterval(timer);};}, []);
|
||||
const [progress, setProgress] = useState('');
|
||||
const active = useRef(true);
|
||||
useEffect(() => {active.current = true; return () => {active.current = false;};}, []);
|
||||
const available = images.data?.filter(selectableImage) || [];
|
||||
@@ -17,21 +12,7 @@ export function CreateInstance({close, refresh}: {close: () => void; refresh: ()
|
||||
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; build_allowed:boolean}>('/latest');
|
||||
if (!latest.version) throw new Error('服务器未返回精确版本。');
|
||||
setVersion(latest.version);
|
||||
if (!latest.build_allowed || !normalBuildAllowed(await api.compatibility(), latest.version)) throw new Error('此最新版不支持普通构建。管理员可前往设置 → 镜像与构建进行实验构建,但实验镜像不能直接用于创建。');
|
||||
return;
|
||||
}
|
||||
if (!normalBuildAllowed(await api.compatibility(), version)) throw new Error('此版本不支持普通构建;实验镜像不能直接用于创建。');
|
||||
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;
|
||||
}
|
||||
const image = available.find(i => (i.image_id || i.id) === form.get('image'));
|
||||
if (!active.current) return;
|
||||
if (!image) throw new Error('请选择可用的本地镜像。');
|
||||
setProgress('正在创建实例,请勿重复提交…');
|
||||
@@ -41,5 +22,12 @@ export function CreateInstance({close, refresh}: {close: () => void; refresh: ()
|
||||
});
|
||||
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' && <ResourceState resource={compatibility}/>} {mode === 'latest' && version && compatibility.data && !normalBuildAllowed(compatibility.data, version) && <p className="alert">此最新版不支持普通构建。管理员可前往设置 → 镜像与构建实验构建;实验镜像不能直接用于创建。</p>}{mode === 'latest' && version && compatibility.data && normalBuildAllowed(compatibility.data, version) && <p>将锁定精确版本 <strong>{version}</strong>,确认后预留名额并构建创建。</p>}<button className="primary" disabled={mode === 'local' ? !available.length : !!version && (!compatibility.data || !normalBuildAllowed(compatibility.data, version))}>{mode === 'local' ? '确认创建' : version ? '确认版本并预留创建' : '查询最新版'}</button></fieldset>{progress && <p role="status">{progress}</p>}{pollError && <p className="muted" role="alert">意图刷新失败:{pollError}</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" disabled={action.busy} onClick={close}>关闭(保留已提交意图)</button></form>;
|
||||
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>
|
||||
</form>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user