import { useState, type FormEvent } from 'react';
import { api, normalBuildAllowed, type AdmissionState, type Build } from './api';
import { Panel, ResourceState, Empty, Status, date, useAction, useResource } from './ui';
import { BuildProvenance, ReleasePanel } from './ReleasePanel';
export const activeBuild = (build: Build) => ['queued', 'running', 'validating'].includes(build.status);
function BuildLogs({id, close}: {id: string; close: () => void}) {
const logs = useResource(() => api.build(id), id, 5000, activeBuild);
return
构建日志
{logs.data && <>
{logs.data.experimental && 实验构建 · 不自动准入}{logs.data.version} ·
{logs.data.error && {logs.data.error}
}{logs.data.logs || '任务尚未输出日志'}
>} ;
}
function Admission({imageId, refresh}: {imageId:string; refresh:() => Promise}) {
const [state, setState] = useState('compatible'), [evidence, setEvidence] = useState(''), [confirmed, setConfirmed] = useState(false);
const action = useAction();
const submittedEvidence = evidence.trim(), evidenceBytes = new TextEncoder().encode(submittedEvidence).length;
const evidenceInvalid = evidenceBytes === 0 || evidenceBytes > 4096;
return ;
}
export function Images({admin = false}: {admin?:boolean}) {
const [experimental, setExperimental] = useState(false), [admissionId, setAdmissionId] = useState();
const [page, setPage] = useState(0);
const compatibility = useResource(api.compatibility);
const images = useResource(api.images), builds = useResource(() => api.builds(21, page * 20), String(page), 5000, rows => rows.slice(0, 20).some(activeBuild)); const action = useAction();
const [latest, setLatest] = useState<{version:string; checked_at?:string}>(), [version,setVersion] = useState(''), [selected,setSelected] = useState();
function build(e: FormEvent) {e.preventDefault(); void action.run(async () => {if (!(admin && experimental) && !normalBuildAllowed(await api.compatibility(), version)) throw new Error('此版本不支持普通构建,请管理员选择实验构建;实验构建不会自动准入。'); await api.startBuild(version, admin && experimental); if (page === 0) await builds.refresh(); else setPage(0);}, '构建任务已提交,请查看任务的实际状态。', (experimental ? '实验构建不会自动准入或用于创建。确认实验构建精确版本 ' : '确认在本机手动构建精确版本 ') + version + '?构建会消耗磁盘、网络与 CPU,不会自动升级实例。');}
const rows = builds.data?.slice(0, 20);
function changePage(next: number) { setSelected(undefined); setPage(next); }
return <>{action.feedback}{admin && {if (page === 0) await builds.refresh(); else changePage(0);}}/>}
{version && compatibility.data && !normalBuildAllowed(compatibility.data, version) && 此版本不支持普通构建。仅管理员可实验构建,不会自动准入。
}
镜像仅保存在本机。构建成功不等于通过验收或自动准入。仅 available 且允许选择的镜像可创建或升级;existing_only 仅允许存量启停。blocked 禁止新建、升级及新的启动/重启,但不会自动停止已运行实例或断开连接。
刷新}>
{images.data?.length === 0 && 本机暂无托管镜像,请先手动构建。}{!!images.data?.length &&
| DSH 版本 |
镜像标识 |
标签 |
状态 |
使用/操作 |
{images.data.map(i =>
|
{i.version}
|
{i.image_id || i.id} |
{i.tag || '—'} |
{({recommended:'推荐',compatible:'兼容',existing_only:'仅存量',blocked:'禁止使用'} as Record)[i.admission || ''] || '未验证'}
{i.admission_reason || '无准入依据'}
{i.selectable && i.status === 'available' ? '可创建 / 升级' : '不可创建 / 升级'}
|
{i.usage_count ?? 0} 个实例 {admin && }{admissionId === (i.image_id || i.id) && admin && } {admin && } |
)}
}
当前页有活跃任务时每 5 秒刷新
>}>
{builds.data?.length === 0 && 没有构建任务记录}{!!builds.data?.length &&
| 任务 |
版本 |
状态 |
提交时间 |
日志 |
{rows!.map(b =>
| {b.id} |
{b.version}{b.experimental && 实验构建 · 不自动准入} |
|
{date(b.created_at)} |
|
)}
}
第 {page + 1} 页
{selected && setSelected(undefined)}/>}
>;
}