feat: 全站 PageShell 设计系统与专业 UI 统一

- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架
- index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩)
- 全站路由页接入 PageShell,云账单三 Tab 为标杆
- 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化
- 登录页/侧栏/顶栏去渐变光球,企业克制风
- 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
Logan committed 2026-07-01 12:45:38 +08:00
1 parent 1acb0c49af
commit e363ded445
213 files changed
+7148 -6818

No files matched your search

@@ -28,67 +28,67 @@ import type {
export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
const c = container;
const sectionBtn = "flex items-center gap-1.5 text-xs font-semibold text-slate-300 mb-2";
const sectionBtn = "flex items-center gap-1.5 text-xs font-semibold text-[var(--dg-text-secondary)] mb-2";
return (
<>
{/* ---- 基本设置 ---- */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<Label className="text-slate-400 text-xs">容器名称</Label>
<Label className="dg-text-secondary text-xs">容器名称</Label>
<Input value={c.name} onChange={e => onUpdate({ name: e.target.value })}
placeholder="my-app"
className="mt-1 bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="mt-1 dg-input font-mono text-xs h-8" />
</div>
<div>
<Label className="text-slate-400 text-xs">容器镜像</Label>
<Label className="dg-text-secondary text-xs">容器镜像</Label>
<Input value={c.image} onChange={e => onUpdate({ image: e.target.value })}
placeholder="nginx:1.25"
className="mt-1 bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="mt-1 dg-input font-mono text-xs h-8" />
</div>
<div>
<Label className="text-slate-400 text-xs">镜像拉取策略</Label>
<Label className="dg-text-secondary text-xs">镜像拉取策略</Label>
<Select value={c.imagePullPolicy || '__none'} onValueChange={v => onUpdate({ imagePullPolicy: v === '__none' ? '' : v })}>
<SelectTrigger className="mt-1 bg-slate-800 border-slate-700 text-slate-200 text-xs h-8">
<SelectTrigger className="mt-1 dg-input text-xs h-8">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-700">
<SelectItem value="__none" className="text-slate-400">不设置(默认 IfNotPresent)</SelectItem>
<SelectItem value="Always" className="text-slate-200">Always</SelectItem>
<SelectItem value="IfNotPresent" className="text-slate-200">IfNotPresent</SelectItem>
<SelectItem value="Never" className="text-slate-200">Never</SelectItem>
<SelectContent className="dg-input">
<SelectItem value="__none" className="dg-text-secondary">不设置(默认 IfNotPresent)</SelectItem>
<SelectItem value="Always" className="text-[var(--dg-text-primary)]">Always</SelectItem>
<SelectItem value="IfNotPresent" className="text-[var(--dg-text-primary)]">IfNotPresent</SelectItem>
<SelectItem value="Never" className="text-[var(--dg-text-primary)]">Never</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label className="text-slate-400 text-xs">工作目录 (workingDir)</Label>
<Label className="dg-text-secondary text-xs">工作目录 (workingDir)</Label>
<Input value={c.workingDir || ''} onChange={e => onUpdate({ workingDir: e.target.value })}
placeholder="/app"
className="mt-1 bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="mt-1 dg-input font-mono text-xs h-8" />
</div>
</div>
{/* ---- 命令与参数 ---- */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<Label className="text-slate-400 text-xs">启动命令 (command) — 每行一个</Label>
<Label className="dg-text-secondary text-xs">启动命令 (command) — 每行一个</Label>
<Textarea value={arrToText(c.command)} placeholder={"/bin/sh\n-c"}
onChange={e => onUpdate({ command: textToArr(e.target.value) })}
className="mt-1 bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs min-h-[60px]" />
className="mt-1 dg-input font-mono text-xs min-h-[60px]" />
</div>
<div>
<Label className="text-slate-400 text-xs">启动参数 (args) — 每行一个</Label>
<Label className="dg-text-secondary text-xs">启动参数 (args) — 每行一个</Label>
<Textarea value={arrToText(c.args)} placeholder={"--config=/etc/app.yaml\n--debug"}
onChange={e => onUpdate({ args: textToArr(e.target.value) })}
className="mt-1 bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs min-h-[60px]" />
className="mt-1 dg-input font-mono text-xs min-h-[60px]" />
</div>
</div>
{/* ---- 环境变量 ---- */}
<div>
<div className="flex items-center justify-between mb-2">
<div className={sectionBtn}><Terminal className="h-3.5 w-3.5 text-cyan-400" /> 环境变量</div>
<Button size="sm" variant="outline" className="h-6 border-slate-700 text-slate-300 text-[11px]"
<div className={sectionBtn}><Terminal className="h-3.5 w-3.5 dg-text-cyan" /> 环境变量</div>
<Button size="sm" variant="outline" className="h-6 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-[11px]"
onClick={() => onUpdate({ env: [...c.env, { name: '', value: '' }] })}>
<Plus className="h-3 w-3 mr-1" />新增
</Button>
@@ -98,17 +98,17 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
<div key={ei} className="grid grid-cols-[1fr_1.4fr_auto] gap-2">
<Input value={env.name} placeholder="KEY"
onChange={e => { const n = [...c.env]; n[ei] = { ...env, name: e.target.value }; onUpdate({ env: n }); }}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
<Input value={env.value || ''} placeholder="value"
onChange={e => { const n = [...c.env]; n[ei] = { ...env, value: e.target.value }; onUpdate({ env: n }); }}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
<Button variant="ghost" size="icon" className="h-8 w-8 text-red-400"
className="dg-input font-mono text-xs h-8" />
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-red"
onClick={() => onUpdate({ env: c.env.filter((_, j) => j !== ei) })}>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
))}
{c.env.length === 0 && <p className="text-[11px] text-slate-600">无环境变量</p>}
{c.env.length === 0 && <p className="text-[11px] dg-text-muted">无环境变量</p>}
</div>
</div>
@@ -116,7 +116,7 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
<div>
<div className="flex items-center justify-between mb-2">
<div className={sectionBtn}><Network className="h-3.5 w-3.5 text-indigo-400" /> 端口</div>
<Button size="sm" variant="outline" className="h-6 border-slate-700 text-slate-300 text-[11px]"
<Button size="sm" variant="outline" className="h-6 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-[11px]"
onClick={() => onUpdate({ ports: [...c.ports, { name: '', containerPort: '', protocol: 'TCP' }] })}>
<Plus className="h-3 w-3 mr-1" />新增
</Button>
@@ -126,57 +126,57 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
<div key={pi} className="grid grid-cols-[1fr_1fr_100px_auto] gap-2">
<Input value={p.name || ''} placeholder="http"
onChange={e => { const n = [...c.ports]; n[pi] = { ...p, name: e.target.value }; onUpdate({ ports: n }); }}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
<Input type="number" value={p.containerPort} placeholder="8080"
onChange={e => { const n = [...c.ports]; n[pi] = { ...p, containerPort: e.target.value }; onUpdate({ ports: n }); }}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
<Select value={p.protocol || 'TCP'} onValueChange={v => { const n = [...c.ports]; n[pi] = { ...p, protocol: v }; onUpdate({ ports: n }); }}>
<SelectTrigger className="bg-slate-800 border-slate-700 text-slate-200 text-xs h-8">
<SelectTrigger className="dg-input text-xs h-8">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-700">
<SelectItem value="TCP" className="text-slate-200">TCP</SelectItem>
<SelectItem value="UDP" className="text-slate-200">UDP</SelectItem>
<SelectItem value="SCTP" className="text-slate-200">SCTP</SelectItem>
<SelectContent className="dg-input">
<SelectItem value="TCP" className="text-[var(--dg-text-primary)]">TCP</SelectItem>
<SelectItem value="UDP" className="text-[var(--dg-text-primary)]">UDP</SelectItem>
<SelectItem value="SCTP" className="text-[var(--dg-text-primary)]">SCTP</SelectItem>
</SelectContent>
</Select>
<Button variant="ghost" size="icon" className="h-8 w-8 text-red-400"
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-red"
onClick={() => onUpdate({ ports: c.ports.filter((_, j) => j !== pi) })}>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
))}
{c.ports.length === 0 && <p className="text-[11px] text-slate-600">无端口暴露</p>}
{c.ports.length === 0 && <p className="text-[11px] dg-text-muted">无端口暴露</p>}
</div>
</div>
{/* ---- 资源 requests/limits ---- */}
<div>
<div className={sectionBtn}><Cpu className="h-3.5 w-3.5 text-amber-400" /> 资源 (requests / limits)</div>
<div className={sectionBtn}><Cpu className="h-3.5 w-3.5 dg-text-yellow" /> 资源 (requests / limits)</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
<div>
<Label className="text-[11px] text-slate-500">CPU 请求</Label>
<Label className="text-[11px] dg-text-muted">CPU 请求</Label>
<Input value={c.resources.requests?.cpu || ''} placeholder="100m"
onChange={e => onUpdate({ resources: { ...c.resources, requests: { ...c.resources.requests, cpu: e.target.value } } })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
</div>
<div>
<Label className="text-[11px] text-slate-500">CPU 上限</Label>
<Label className="text-[11px] dg-text-muted">CPU 上限</Label>
<Input value={c.resources.limits?.cpu || ''} placeholder="500m"
onChange={e => onUpdate({ resources: { ...c.resources, limits: { ...c.resources.limits, cpu: e.target.value } } })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
</div>
<div>
<Label className="text-[11px] text-slate-500">内存请求</Label>
<Label className="text-[11px] dg-text-muted">内存请求</Label>
<Input value={c.resources.requests?.memory || ''} placeholder="128Mi"
onChange={e => onUpdate({ resources: { ...c.resources, requests: { ...c.resources.requests, memory: e.target.value } } })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
</div>
<div>
<Label className="text-[11px] text-slate-500">内存上限</Label>
<Label className="text-[11px] dg-text-muted">内存上限</Label>
<Input value={c.resources.limits?.memory || ''} placeholder="512Mi"
onChange={e => onUpdate({ resources: { ...c.resources, limits: { ...c.resources.limits, memory: e.target.value } } })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
</div>
</div>
</div>
@@ -197,7 +197,7 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
{/* ---- 生命周期 ---- */}
<div>
<div className={sectionBtn}><Zap className="h-3.5 w-3.5 text-yellow-400" /> 生命周期回调 (Lifecycle)</div>
<div className={sectionBtn}><Zap className="h-3.5 w-3.5 dg-text-yellow" /> 生命周期回调 (Lifecycle)</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<LifecycleHandlerEditor label="启动后 (postStart)" handler={c.lifecycle?.postStart}
onChange={h => onUpdate({ lifecycle: { ...c.lifecycle, postStart: h } })} />
@@ -205,7 +205,7 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
onChange={h => onUpdate({ lifecycle: { ...c.lifecycle, preStop: h } })} />
</div>
{!c.lifecycle && (
<Button size="sm" variant="outline" className="h-6 border-slate-700 text-slate-300 text-[11px] mt-1"
<Button size="sm" variant="outline" className="h-6 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-[11px] mt-1"
onClick={() => onUpdate({ lifecycle: { postStart: { type: 'none' }, preStop: { type: 'none' } } })}>
<Plus className="h-3 w-3 mr-1" />启用生命周期回调
</Button>
@@ -214,33 +214,33 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
{/* ---- 安全上下文 ---- */}
<div>
<div className={sectionBtn}><Shield className="h-3.5 w-3.5 text-emerald-400" /> 安全上下文 (SecurityContext)</div>
<div className={sectionBtn}><Shield className="h-3.5 w-3.5 dg-text-green" /> 安全上下文 (SecurityContext)</div>
{c.securityContext ? (
<div className="space-y-2 border border-slate-800 rounded-md p-3 bg-slate-950/30">
<div className="space-y-2 border border-[var(--dg-border)] rounded-md p-3 bg-[var(--dg-bg-primary)]/30">
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
<div>
<Label className="text-[11px] text-slate-500">runAsUser</Label>
<Label className="text-[11px] dg-text-muted">runAsUser</Label>
<Input type="number" value={c.securityContext.runAsUser ?? ''} placeholder="1000"
onChange={e => onUpdate({ securityContext: { ...c.securityContext!, runAsUser: e.target.value === '' ? undefined : Number(e.target.value) } })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
</div>
<div>
<Label className="text-[11px] text-slate-500">runAsGroup</Label>
<Label className="text-[11px] dg-text-muted">runAsGroup</Label>
<Input type="number" value={c.securityContext.runAsGroup ?? ''} placeholder="1000"
onChange={e => onUpdate({ securityContext: { ...c.securityContext!, runAsGroup: e.target.value === '' ? undefined : Number(e.target.value) } })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
</div>
<div>
<Label className="text-[11px] text-slate-500">Capabilities Add (逗号分隔)</Label>
<Label className="text-[11px] dg-text-muted">Capabilities Add (逗号分隔)</Label>
<Input value={(c.securityContext.capabilitiesAdd || []).join(',')} placeholder="NET_ADMIN"
onChange={e => onUpdate({ securityContext: { ...c.securityContext!, capabilitiesAdd: e.target.value.split(',').map(s => s.trim()).filter(Boolean) } })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
</div>
<div>
<Label className="text-[11px] text-slate-500">Capabilities Drop (逗号分隔)</Label>
<Label className="text-[11px] dg-text-muted">Capabilities Drop (逗号分隔)</Label>
<Input value={(c.securityContext.capabilitiesDrop || []).join(',')} placeholder="ALL"
onChange={e => onUpdate({ securityContext: { ...c.securityContext!, capabilitiesDrop: e.target.value.split(',').map(s => s.trim()).filter(Boolean) } })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
</div>
</div>
<div className="flex items-center gap-4 flex-wrap">
@@ -253,13 +253,13 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
<BoolToggle label="allowPrivilegeEscalation" value={c.securityContext.allowPrivilegeEscalation ?? false}
onChange={v => onUpdate({ securityContext: { ...c.securityContext!, allowPrivilegeEscalation: v } })} />
</div>
<Button size="sm" variant="ghost" className="h-6 text-red-400 text-[11px]"
<Button size="sm" variant="ghost" className="h-6 dg-text-red text-[11px]"
onClick={() => onUpdate({ securityContext: null })}>
<Trash2 className="h-3 w-3 mr-1" />移除安全上下文
</Button>
</div>
) : (
<Button size="sm" variant="outline" className="h-6 border-slate-700 text-slate-300 text-[11px]"
<Button size="sm" variant="outline" className="h-6 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-[11px]"
onClick={() => onUpdate({ securityContext: { runAsNonRoot: false, privileged: false } })}>
<Plus className="h-3 w-3 mr-1" />启用安全上下文
</Button>
@@ -270,7 +270,7 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
<div>
<div className="flex items-center justify-between mb-2">
<div className={sectionBtn}><FolderOpen className="h-3.5 w-3.5 text-teal-400" /> 卷挂载 (VolumeMounts)</div>
<Button size="sm" variant="outline" className="h-6 border-slate-700 text-slate-300 text-[11px]"
<Button size="sm" variant="outline" className="h-6 border-[var(--dg-border)] text-[var(--dg-text-secondary)] text-[11px]"
onClick={() => onUpdate({ volumeMounts: [...c.volumeMounts, { name: '', mountPath: '', subPath: '', readOnly: false }] })}>
<Plus className="h-3 w-3 mr-1" />新增
</Button>
@@ -280,25 +280,25 @@ export function ContainerEditor({ container, onUpdate }: ContainerEditorProps) {
<div key={mi} className="grid grid-cols-[1fr_1fr_1fr_60px_auto] gap-2 items-center">
<Input value={m.name} placeholder="volume 名称"
onChange={e => { const n = [...c.volumeMounts]; n[mi] = { ...m, name: e.target.value }; onUpdate({ volumeMounts: n }); }}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
<Input value={m.mountPath} placeholder="/data"
onChange={e => { const n = [...c.volumeMounts]; n[mi] = { ...m, mountPath: e.target.value }; onUpdate({ volumeMounts: n }); }}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
className="dg-input font-mono text-xs h-8" />
<Input value={m.subPath || ''} placeholder="subPath (可选)"
onChange={e => { const n = [...c.volumeMounts]; n[mi] = { ...m, subPath: e.target.value }; onUpdate({ volumeMounts: n }); }}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-8" />
<label className="flex items-center gap-1 text-[10px] text-slate-500 cursor-pointer">
className="dg-input font-mono text-xs h-8" />
<label className="flex items-center gap-1 text-[10px] dg-text-muted cursor-pointer">
<Switch checked={!!m.readOnly}
onCheckedChange={v => { const n = [...c.volumeMounts]; n[mi] = { ...m, readOnly: v }; onUpdate({ volumeMounts: n }); }} />
RO
</label>
<Button variant="ghost" size="icon" className="h-8 w-8 text-red-400"
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-red"
onClick={() => onUpdate({ volumeMounts: c.volumeMounts.filter((_, j) => j !== mi) })}>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
))}
{c.volumeMounts.length === 0 && <p className="text-[11px] text-slate-600">无卷挂载</p>}
{c.volumeMounts.length === 0 && <p className="text-[11px] dg-text-muted">无卷挂载</p>}
</div>
</div>
</>
@@ -312,24 +312,24 @@ function ProbeEditor({ label, probe, onChange }: ProbeEditorProps) {
const setField = (patch: Partial<ProbeDef>) => onChange({ ...p, ...patch });
return (
<div className="border border-slate-800 rounded-md p-2.5 bg-slate-950/30">
<div className="border border-[var(--dg-border)] rounded-md p-2.5 bg-[var(--dg-bg-primary)]/30">
<div className="flex items-center justify-between mb-2">
<span className="text-xs text-slate-300 font-medium">{label}</span>
<span className="text-xs text-[var(--dg-text-secondary)] font-medium">{label}</span>
<Switch checked={enabled}
onCheckedChange={v => onChange(v ? { type: 'httpGet', httpPath: '/', httpPort: 8080, initialDelaySeconds: 10, periodSeconds: 10 } : null)} />
</div>
{enabled && (
<>
<div className="flex items-center gap-2 mb-2">
<span className="text-[10px] text-slate-500 w-8">类型</span>
<span className="text-[10px] dg-text-muted w-8">类型</span>
<Select value={p.type || 'httpGet'} onValueChange={v => setField({ type: v as ProbeDef['type'] })}>
<SelectTrigger className="bg-slate-800 border-slate-700 text-slate-200 text-xs h-7 flex-1">
<SelectTrigger className="dg-input text-xs h-7 flex-1">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-700">
<SelectItem value="httpGet" className="text-slate-200">HTTP GET</SelectItem>
<SelectItem value="tcpSocket" className="text-slate-200">TCP Socket</SelectItem>
<SelectItem value="exec" className="text-slate-200">Exec 命令</SelectItem>
<SelectContent className="dg-input">
<SelectItem value="httpGet" className="text-[var(--dg-text-primary)]">HTTP GET</SelectItem>
<SelectItem value="tcpSocket" className="text-[var(--dg-text-primary)]">TCP Socket</SelectItem>
<SelectItem value="exec" className="text-[var(--dg-text-primary)]">Exec 命令</SelectItem>
</SelectContent>
</Select>
</div>
@@ -337,59 +337,59 @@ function ProbeEditor({ label, probe, onChange }: ProbeEditorProps) {
<div className="grid grid-cols-3 gap-2 mb-2">
<Input value={p.httpPath || ''} placeholder="/healthz"
onChange={e => setField({ httpPath: e.target.value })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
<Input type="number" value={p.httpPort ?? ''} placeholder="8080"
onChange={e => setField({ httpPort: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
<Input value={p.httpHost || ''} placeholder="host (可选)"
onChange={e => setField({ httpHost: e.target.value })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
</div>
)}
{p.type === 'tcpSocket' && (
<div className="mb-2">
<Input type="number" value={p.tcpPort ?? ''} placeholder="端口"
onChange={e => setField({ tcpPort: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
</div>
)}
{p.type === 'exec' && (
<div className="mb-2">
<Textarea value={p.execCommand || ''} placeholder="每行一个命令片段,如:\ncat\n/tmp/healthy"
onChange={e => setField({ execCommand: e.target.value })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs min-h-[50px]" />
className="dg-input font-mono text-xs min-h-[50px]" />
</div>
)}
<div className="grid grid-cols-5 gap-2">
<div>
<Label className="text-[10px] text-slate-500">延迟(s)</Label>
<Label className="text-[10px] dg-text-muted">延迟(s)</Label>
<Input type="number" value={p.initialDelaySeconds ?? ''} placeholder="10"
onChange={e => setField({ initialDelaySeconds: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
</div>
<div>
<Label className="text-[10px] text-slate-500">间隔(s)</Label>
<Label className="text-[10px] dg-text-muted">间隔(s)</Label>
<Input type="number" value={p.periodSeconds ?? ''} placeholder="10"
onChange={e => setField({ periodSeconds: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
</div>
<div>
<Label className="text-[10px] text-slate-500">超时(s)</Label>
<Label className="text-[10px] dg-text-muted">超时(s)</Label>
<Input type="number" value={p.timeoutSeconds ?? ''} placeholder="1"
onChange={e => setField({ timeoutSeconds: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
</div>
<div>
<Label className="text-[10px] text-slate-500">成功</Label>
<Label className="text-[10px] dg-text-muted">成功</Label>
<Input type="number" value={p.successThreshold ?? ''} placeholder="1"
onChange={e => setField({ successThreshold: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
</div>
<div>
<Label className="text-[10px] text-slate-500">失败</Label>
<Label className="text-[10px] dg-text-muted">失败</Label>
<Input type="number" value={p.failureThreshold ?? ''} placeholder="3"
onChange={e => setField({ failureThreshold: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
</div>
</div>
</>
@@ -403,41 +403,41 @@ function LifecycleHandlerEditor({ label, handler, onChange }: LifecycleHandlerEd
const selVal = (!h.type || h.type === 'none') ? '__none' : h.type;
return (
<div className="border border-slate-800 rounded-md p-2.5 bg-slate-950/30">
<span className="text-xs text-slate-300 font-medium block mb-2">{label}</span>
<div className="border border-[var(--dg-border)] rounded-md p-2.5 bg-[var(--dg-bg-primary)]/30">
<span className="text-xs text-[var(--dg-text-secondary)] font-medium block mb-2">{label}</span>
<div className="flex items-center gap-2 mb-2">
<span className="text-[10px] text-slate-500 w-8">类型</span>
<span className="text-[10px] dg-text-muted w-8">类型</span>
<Select value={selVal} onValueChange={v => onChange({ ...h, type: (v === '__none' ? 'none' : v) as LifecycleHandler['type'] })}>
<SelectTrigger className="bg-slate-800 border-slate-700 text-slate-200 text-xs h-7 flex-1">
<SelectTrigger className="dg-input text-xs h-7 flex-1">
<SelectValue />
</SelectTrigger>
<SelectContent className="bg-slate-900 border-slate-700">
<SelectItem value="__none" className="text-slate-400">不设置</SelectItem>
<SelectItem value="exec" className="text-slate-200">Exec 命令</SelectItem>
<SelectItem value="httpGet" className="text-slate-200">HTTP GET</SelectItem>
<SelectItem value="tcpSocket" className="text-slate-200">TCP Socket</SelectItem>
<SelectContent className="dg-input">
<SelectItem value="__none" className="dg-text-secondary">不设置</SelectItem>
<SelectItem value="exec" className="text-[var(--dg-text-primary)]">Exec 命令</SelectItem>
<SelectItem value="httpGet" className="text-[var(--dg-text-primary)]">HTTP GET</SelectItem>
<SelectItem value="tcpSocket" className="text-[var(--dg-text-primary)]">TCP Socket</SelectItem>
</SelectContent>
</Select>
</div>
{h.type === 'exec' && (
<Textarea value={h.execCommand || ''} placeholder="每行一个命令片段"
onChange={e => onChange({ ...h, execCommand: e.target.value })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs min-h-[50px]" />
className="dg-input font-mono text-xs min-h-[50px]" />
)}
{h.type === 'httpGet' && (
<div className="grid grid-cols-2 gap-2">
<Input value={h.httpPath || ''} placeholder="/shutdown"
onChange={e => onChange({ ...h, httpPath: e.target.value })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
<Input type="number" value={h.httpPort ?? ''} placeholder="8080"
onChange={e => onChange({ ...h, httpPort: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
</div>
)}
{h.type === 'tcpSocket' && (
<Input type="number" value={h.tcpPort ?? ''} placeholder="端口"
onChange={e => onChange({ ...h, tcpPort: e.target.value === '' ? undefined : Number(e.target.value) })}
className="bg-slate-800 border-slate-700 text-slate-200 font-mono text-xs h-7" />
className="dg-input font-mono text-xs h-7" />
)}
</div>
);
@@ -447,7 +447,7 @@ function BoolToggle({ label, value, onChange }: BoolToggleProps) {
return (
<label className="flex items-center gap-1.5 cursor-pointer">
<Switch checked={value} onCheckedChange={onChange} />
<span className="text-[11px] text-slate-400 font-mono">{label}</span>
<span className="text-[11px] dg-text-secondary font-mono">{label}</span>
</label>
);
}