import { useState } from 'react'; import { ExternalLink, Plus, RefreshCw, Trash2 } from 'lucide-react'; import { ProfileEditor } from './ProfileEditor'; import { BackupPanel } from './BackupPanel'; import { CreateInstance } from './CreateInstance'; import { api, selectableImage, instancePath, type Instance } from './api'; import { Panel, ResourceState, Empty, Status, bytes, date, useAction, useResource, tableClass, tableHeadRowClass, thClass, tdClass, trClass } from './ui'; import { useConfirm } from './components/ConfirmDialog'; import { buttonVariants } from './components/ui/button'; import { inputClass, selectClass, selectStyle } from './components/ui/input'; import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/ui/tabs'; import { AlertBox } from './components/ui/alert'; import { cn } from './lib/utils'; export function Instances() { const instances = useResource(api.instances, '', 10000), usage = useResource(api.usage); const action = useAction(); const [creating, setCreating] = useState(false), [selected, setSelected] = useState(); const confirm = useConfirm(); async function operate(i: Instance, operation: string) { if (operation === 'delete') { if (!await confirm.ask({title: '删除实例', danger: true, confirmLabel: '永久删除', body: <>确认删除实例「{i.name}」?此操作不可撤销,将永久删除该实例的文件、配置、快照及备份。请先下载需要保留的数据。})) return; } else if (operation !== 'start') { if (!await confirm.ask({title: '停止或重启实例', confirmLabel: '确认执行', body: <>确认停止或重启「{i.name}」?当前连接将中断。})) return; } void action.run(async () => { await api.request(instancePath(i.id) + (operation === 'delete' ? '' : '/' + operation), operation === 'delete' ? 'DELETE' : 'POST'); if (selected?.id === i.id && operation === 'delete') setSelected(undefined); await instances.refresh(); if (operation === 'delete') await usage.refresh(); }, '服务器已完成操作'); } const stats = [ {label: '我的实例', value: instances.data ? instances.data.length : '—', suffix: ' / 2', hint: '每个账号最多两个实例'}, {label: '运行中', value: instances.data ? instances.data.filter(i => i.status === 'running').length : '—', suffix: '', hint: '独立的 DSH 运行环境'}, {label: '工作区用量', value: bytes(usage.data?.used_bytes), suffix: '', hint: '配额 ' + bytes(usage.data?.quota_bytes)}, ]; return <> {confirm.element}
{stats.map(s =>
{s.label} {s.value}{s.suffix}

{s.hint}

)}
{action.feedback} }> {creating && setCreating(false)} refresh={instances.refresh} />} {instances.data?.length === 0 && 还没有实例。选择本地镜像,创建你的第一个开发环境。} {!!instances.data?.length && <> {/* Desktop table */}
{instances.data.map(i => )}
实例 版本 状态 创建时间 操作
{i.name}{i.id} {i.version} {date(i.created_at)}
{i.status === 'running' ? <>打开 DSH : }
{/* Mobile cards */}
{instances.data.map(i =>
{i.name}

{i.id}

版本 {i.version} · 创建于 {date(i.created_at)}

{i.status === 'running' ? <>打开 DSH : }
)}
}
{selected && i.id === selected.id) || selected} close={() => setSelected(undefined)} refresh={async () => { await Promise.all([instances.refresh(), usage.refresh()]); }} />} ; } function InstanceDetail({instance, close, refresh}: {instance: Instance; close: () => void; refresh: () => Promise}) { const [content, setContent] = useState(); const action = useAction(); const confirm = useConfirm(); const base = instancePath(instance.id); const stopped = ['stopped', 'exited', 'created'].includes(instance.status); const images = useResource(api.images); const available = images.data?.filter(selectableImage) || []; const [upgradeImage, setUpgradeImage] = useState(''); const [upgradePassword, setUpgradePassword] = useState(''); const profile = useResource(() => api.request<{content: string}>(base + '/profile')); const preview = useResource(() => api.request<{bundles: unknown; config: string; preview_error?: string}>(base + '/profile-preview')); const logs = useResource(() => api.request<{logs: string}>(base + '/logs')); return 关闭}> {confirm.element} Profile 配置 配置预览 备份与恢复 版本升级 运行日志 {action.feedback} Profile 写入前需先停止实例。当前状态: {!stopped && }

profiles/web/cordis.patch.yml · 停止实例后保存,保存后自动启动验证。密钥原文不在此展示。

{profile.data && <>
}
{preview.data && <>

Bundle 清单

{JSON.stringify(preview.data.bundles, null, 2)}
{preview.data.preview_error &&

{preview.data.preview_error}

} }
{ await profile.refresh(); setContent(undefined); await refresh(); }} />
{ e.preventDefault(); if (!available.some(i => (i.image_id || i.id) === upgradeImage)) return; if (!await confirm.ask({title: '升级实例', confirmLabel: '升级实例', body: '确认使用所选本地镜像升级?建议先备份配置。升级会自动编排停启并保留 home 快照。'})) return; void action.run(async () => { try { await api.request(base + '/upgrade', 'POST', {image_id: upgradeImage, password: upgradePassword}); await refresh(); } finally { setUpgradePassword(''); } }, '实例升级请求已完成。'); }}>

当前版本:{instance.version}

仅已准入且本地可用的镜像可用于升级。历史镜像仅允许存量实例运行,不可用于新建或升级。
{logs.data?.logs || '暂无日志'}
; }