feat: 完善堡垒机与 Agent 智能运维能力

- 支持 Windows Agent 构建、安装脚本和统一上报协议

- 增强 RDP 剪贴板、虚拟盘和文件传输能力

- 新增 AIOps 上下文工具、告警 RCA trace 落库和回放接口

- 重构 docs 模块文档并补充终端、堡垒机、监控、AI Agent 设计
This commit is contained in:
Logan.Li committed 2026-07-05 23:49:36 +08:00
1 parent a9513c2056
commit e0d652d211
169 files changed
+12192 -9116

No files matched your search

@@ -77,7 +77,7 @@ export default function CapacityPage() {
return (
<PageShell
variant="list"
title=i18n.t('k8s.capacity.resources')
title={i18n.t('k8s.capacity.resources')}
subtitle={clusterName ? `${clusterName} · Deployment 资源请求与限制` : i18n.t('k8s.capacity.resources1')}
icon={<BarChart3 className="h-5 w-5" />}
actions={(
@@ -89,7 +89,7 @@ export default function CapacityPage() {
</SelectContent>
</Select>
<Select value={selectedNs} onValueChange={setSelectedNs}>
<SelectTrigger className="w-32 h-9 dg-input"><SelectValue placeholder=i18n.t('k8s.capacity.namespace') /></SelectTrigger>
<SelectTrigger className="w-32 h-9 dg-input"><SelectValue placeholder={i18n.t('k8s.capacity.namespace')} /></SelectTrigger>
<SelectContent>
<SelectItem value="all">所有命名空间</SelectItem>
{namespaces.map(n => <SelectItem key={n} value={n}>{n}</SelectItem>)}
@@ -120,7 +120,7 @@ export default function CapacityPage() {
</TableRow></TableHeader>
<TableBody>
{rows.length === 0 ? (
<TableRow><TableCell colSpan={8} className="text-center dg-text-muted py-8i18n.t('k8s.capacity.s934752c9')animate-spin inline-block ml-2">⏳</span> : ''}</TableCell></TableRow>
<TableRow><TableCell colSpan={8} className="text-center dg-text-muted py-8">{i18n.t('k8s.capacity.s934752c9')}</TableCell></TableRow>
) : rows.map((r, i) => (
<TableRow key={`${r.deployment}-${r.container}-${i}`} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]">
<TableCell className="font-medium text-[var(--dg-text-primary)] text-sm">{r.deployment}</TableCell>
@@ -432,7 +432,7 @@ export function DeploymentEditAdvanced({ clusterCode, namespace, deploymentName,
<option value="PreferNoSchedule">PreferNoSchedule</option>
<option value="NoExecute">NoExecute</option>
</select>
<Input value={t.tolerationSeconds} placeholder=i18n.t('k8s.editAdvanced.s0c1fec65') disabled={t.effect !== 'NoExecute'}
<Input value={t.tolerationSeconds} placeholder={i18n.t('k8s.editAdvanced.s0c1fec65')} disabled={t.effect !== 'NoExecute'}
onChange={e => setTolerations(prev => prev.map((x, idx) => idx === i ? { ...x, tolerationSeconds: e.target.value } : x))}
className="h-7 dg-input text-xs font-mono" />
<Button variant="ghost" size="icon" className="h-7 w-7 dg-text-red" onClick={() => setTolerations(prev => prev.filter((_, idx) => idx !== i))}>
@@ -190,7 +190,7 @@ export function KubectlTerminal({ open, onOpenChange, clusterId, clusterName }:
{/* 终端容器 */}
<div className="relative bg-[#0f172a]" style={{ height: fullscreen ? 'calc(100vh - 49px)' : '480px' }}>
<div ref={containerRef} className="xterm-terminal-host h-full" />
<TerminalLoadingOverlay show={!hasOutput && !error} label=i18n.t('k8s.kubectl.cluster') />
<TerminalLoadingOverlay show={!hasOutput && !error} label={i18n.t('k8s.kubectl.cluster')} />
</div>
</DialogContent>
</Dialog>
@@ -473,12 +473,16 @@ export default function NamespaceResourcePage() {
);
const ResourceIcon = config.icon;
const subtitleParts = [clusterCode, namespace].filter(Boolean);
const pageSubtitle = subtitleParts.length > 0
? `${subtitleParts.join(' / ')} · ${config.subtitle}`
: config.subtitle;
return (
<PageShell
variant="list"
title={config.title}
subtitle={`${clusterCode || '-'} / ${namespace || '-'} · ${config.subtitle}`}
subtitle={pageSubtitle}
icon={<ResourceIcon className="h-5 w-5" />}
actions={(
<>
@@ -308,7 +308,7 @@ export function PodExecTerminal({
<Select value={container} onValueChange={setContainer}>
<SelectTrigger className="h-8 w-[150px] text-xs bg-[var(--dg-bg-hover)] border-[var(--dg-border)]">
<Box className="h-3 w-3 mr-1 text-[var(--dg-text-muted)]" />
<SelectValue placeholder=i18n.t('k8s.podExec.container') />
<SelectValue placeholder={i18n.t('k8s.podExec.container')} />
</SelectTrigger>
<SelectContent>{containers.map((c) => <SelectItem key={c} value={c}>{c}</SelectItem>)}</SelectContent>
</Select>
@@ -320,13 +320,13 @@ export function PodExecTerminal({
<SelectContent>{SHELLS.map(s => <SelectItem key={s.value} value={s.value}>{s.label}</SelectItem>)}</SelectContent>
</Select>
<Button variant="ghost" size="sm" className="h-8 px-2 text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]"
onClick={() => { connect(); toast({ title: i18n.t('k8s.podExec.reconnect1') }); }} title=i18n.t('k8s.podExec.reconnect2')>
onClick={() => { connect(); toast({ title: i18n.t('k8s.podExec.reconnect1') }); }} title={i18n.t('k8s.podExec.reconnect2')}>
<RotateCw className="h-3.5 w-3.5" />
</Button>
<button onClick={() => setFullscreen(!fullscreen)} className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]" title={fullscreen ? i18n.t('k8s.podExec.exitFullscreen') : i18n.t('k8s.podExec.fullscreen')}>
{fullscreen ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
</button>
<button onClick={() => onOpenChange(false)} className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]" title=i18n.t('k8s.podExec.close1')>
<button onClick={() => onOpenChange(false)} className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]" title={i18n.t('k8s.podExec.close1')}>
<X className="h-4 w-4" />
</button>
</div>
@@ -339,7 +339,7 @@ export function PodExecTerminal({
className="xterm-terminal-host h-full cursor-text"
onClick={handleTerminalClick}
/>
<TerminalLoadingOverlay show={connState === 'connecting' && !hasOutput} label=i18n.t('k8s.podExec.container1') />
<TerminalLoadingOverlay show={connState === 'connecting' && !hasOutput} label={i18n.t('k8s.podExec.container1')} />
</div>
</DialogContent>
</Dialog>
@@ -86,7 +86,7 @@ export default function PodMetricsPage() {
return (
<PageShell
variant="list"
title=i18n.t('k8s.podMetrics.resources2')
title={i18n.t('k8s.podMetrics.resources2')}
subtitle={clusterName ? `${clusterName} · 容器 CPU/内存实时指标` : i18n.t('k8s.podMetrics.container2')}
icon={<Activity className="h-5 w-5" />}
metrics={!loading ? (
@@ -107,7 +107,7 @@ export default function PodMetricsPage() {
</SelectContent>
</Select>
<Select value={selectedNs} onValueChange={setSelectedNs}>
<SelectTrigger className="w-36 h-9 dg-input"><SelectValue placeholder=i18n.t('k8s.podMetrics.namespace') /></SelectTrigger>
<SelectTrigger className="w-36 h-9 dg-input"><SelectValue placeholder={i18n.t('k8s.podMetrics.namespace')} /></SelectTrigger>
<SelectContent>
<SelectItem value="all">所有命名空间</SelectItem>
{namespaces.map(n => <SelectItem key={n} value={n}>{n}</SelectItem>)}
@@ -131,7 +131,7 @@ export default function PodMetricsPage() {
</TableRow></TableHeader>
<TableBody>
{rows.length === 0 ? (
<TableRow><TableCell colSpan={5} className="text-center dg-text-muted py-8i18n.t('k8s.podMetrics.s934752c9')animate-spin inline-block ml-2">⏳</span> : ''}</TableCell></TableRow>
<TableRow><TableCell colSpan={5} className="text-center dg-text-muted py-8">{i18n.t('k8s.podMetrics.s934752c9')}</TableCell></TableRow>
) : rows.map((r, i) => (
<TableRow key={`${r.pod}-${r.container}-${i}`} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]">
<TableCell className="font-medium text-[var(--dg-text-primary)] text-sm">{r.pod}</TableCell>
@@ -335,7 +335,7 @@ function ConfigMapEditor({ manifest, onSave, saving, isCreate }: { manifest: any
<Textarea
value={entry.value}
onChange={e => update(i, 'value', e.target.value)}
placeholder=i18n.t('k8s.resourceEditor.sfe7509e0')
placeholder={i18n.t('k8s.resourceEditor.sfe7509e0')}
rows={expandAll || entry.value.length > 300 ? 10 : (entry.value.length > 100 ? 6 : 3)}
className="flex-[2] font-mono text-xs min-h-[70px] max-h-[420px] resize-y bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] leading-relaxed"
/>
@@ -98,7 +98,7 @@ export function CreateHPADialog({ clusterCode, namespace, deploymentName, onCrea
className="dg-input mt-1" />
</div>
<div>
<Label className="dg-text-secondary text-smi18n.t('k8s.misc.memory1')dg-text-muted">可选</span></Label>
<Label className="dg-text-secondary text-sm">{i18n.t('k8s.misc.memory1')} <span className="dg-text-muted">可选</span></Label>
<Input type="number" value={form.memPercent} min={0} max={100}
onChange={(e) => setForm({ ...form, memPercent: Number(e.target.value) })}
className="dg-input mt-1" />