diff --git a/deploy/kubernetes/deployment.yaml b/deploy/kubernetes/deployment.yaml index 4b0e9dc..29bdd57 100644 --- a/deploy/kubernetes/deployment.yaml +++ b/deploy/kubernetes/deployment.yaml @@ -35,10 +35,11 @@ spec: spec: hostNetwork: true dnsPolicy: ClusterFirstWithHostNet + enableServiceLinks: false containers: - name: aiops - image: aiops-aiops:latest - imagePullPolicy: IfNotPresent + image: 10.10.10.72/library/aiops:embed + imagePullPolicy: Always ports: - name: http containerPort: 8080 diff --git a/frontend/src/components/kubernetes/DeploymentView.tsx b/frontend/src/components/kubernetes/DeploymentView.tsx index f3598f7..6e96759 100644 --- a/frontend/src/components/kubernetes/DeploymentView.tsx +++ b/frontend/src/components/kubernetes/DeploymentView.tsx @@ -78,6 +78,10 @@ export function DeploymentView(props: DeploymentViewProps) { const [events, setEvents] = useState([]); const [tabLoading, setTabLoading] = useState(''); + // HPA / CronHPA state + const [hpas, setHpas] = useState([]); + const [cronHpas, setCronHpas] = useState([]); + const ready = deployment?.ready_replicas ?? deployment?.readyReplicas ?? 0; const total = deployment?.replicas ?? 0; const isHealthy = ready === total && total > 0; @@ -155,6 +159,22 @@ export function DeploymentView(props: DeploymentViewProps) { }).catch(() => {}).finally(() => setTabLoading('')); }, [tab, clusterCode, namespace, deployment?.name]); + // Load HPA and CronHPA for "伸缩" tab + useEffect(() => { + if (tab !== 'scale') return; + setTabLoading('scale'); + Promise.all([ + kubernetesApi.getHPAs(clusterCode, namespace).catch(() => []), + Promise.resolve([]), // CronHPA 待后端支持 + ]).then(([hpaRes]) => { + const toArr = (v: any) => (Array.isArray(v) ? v : (v?.list || v?.items || v?.data || [])); + const allHpas = toArr(hpaRes); + const depName = deployment?.name || ''; + setHpas(allHpas.filter((h: any) => (h.scaleTargetRef?.name || h.targetRef?.name || '') === depName)); + setCronHpas([]); + }).catch(() => {}).finally(() => setTabLoading('')); + }, [tab, clusterCode, namespace, deployment?.name]); + // Rollback const handleRollback = async (rev: any) => { const revision = rev.revision ?? rev.revision_number ?? rev.number; @@ -223,7 +243,7 @@ export function DeploymentView(props: DeploymentViewProps) { - {/* ============ Tab Bar ============ */} + {/* ============ Tab Bar + 副本数 ============ */}
{TABS.map(t => ( ))} + {/* 副本数控制 — Tab 行右侧 */} +
+ + setScaleValue(Math.max(0, Number(e.target.value)))} + className="w-16 h-7 text-center text-sm font-bold bg-slate-800 border-slate-700 text-slate-200" + /> + + {scaleValue !== total && ( + + )} +
{/* ============ Tab Content ============ */} @@ -247,12 +291,11 @@ export function DeploymentView(props: DeploymentViewProps) { {tab === 'services' && } {tab === 'events' && } {tab === 'scale' && ( - )} {tab === 'history' && } @@ -506,33 +549,122 @@ function EventsTab({ events, loading }: { events: EventItem[]; loading: string } ); } -// ═══════════════ TAB: 容器伸缩 ═══════════════ -function ScaleTab({ current, value, onChange, onApply, loading }: { current: number; value: number; onChange: (v: number) => void; onApply: () => void; loading: boolean }) { +// ═══════════════ TAB: 伸缩 (HPA + CronHPA) ═══════════════ +function HPACronHPATab({ hpas, cronHpas, loading, replicas }: { hpas: any[]; cronHpas: any[]; loading: string; replicas: number }) { + if (loading === 'scale') { + return
; + } + return ( - - -
-

当前副本数: {current}

-
- - onChange(Math.max(0, Number(e.target.value)))} - className="w-28 text-center text-2xl font-bold" - /> - -
- -
-
-
+
+ {/* 当前副本数提示 */} +
+ 当前副本: + {replicas} + (可通过 Tab 栏右侧 +/- 手动调整) +
+ + {/* HPA 策略 */} + + HPA — 水平自动伸缩 + + {hpas.length === 0 ? ( +
+ 未关联 HPA — +
+ ) : ( +
+ + + + 名称 + 最小/最大 + 指标 + 当前负载 + 状态 + + + + {hpas.map((hpa: any, i: number) => { + const minR = hpa.spec?.minReplicas ?? hpa.minReplicas ?? 1; + const maxR = hpa.spec?.maxReplicas ?? hpa.maxReplicas ?? 10; + const metrics = hpa.spec?.metrics || hpa.metrics || []; + const curR = hpa.status?.currentReplicas ?? hpa.currentReplicas ?? 0; + const conditions = hpa.status?.conditions || hpa.conditions || []; + const ableToScale = conditions.find((c: any) => c.type === 'AbleToScale'); + const ok = ableToScale?.status === 'True'; + return ( + + {hpa.name || hpa.metadata?.name} + {minR} → {maxR} + + {metrics.map((m: any, j: number) => { + const resource = m.resource || m.object?.metric?.name || ''; + const target = m.resource?.target?.averageUtilization || m.resource?.target?.averageValue || m.resource?.target?.type || '-'; + return
{resource?.name || resource}: {target}{m.resource?.target?.averageUtilization ? '%' : ''}
; + })} +
+ {curR} + + + {ok ? '正常' : '异常'} + + +
+ ); + })} +
+
+
+ )} +
+
+ + {/* CronHPA 策略 */} + + CronHPA — 定时伸缩 + + {cronHpas.length === 0 ? ( +
+ 未关联 CronHPA — +
+ ) : ( +
+ + + + 名称 + 目标副本 + 调度规则 + 时区 + 状态 + + + + {cronHpas.map((cron: any, i: number) => { + const jobs = cron.spec?.jobs || cron.jobs || []; + const tz = cron.spec?.timezone || 'UTC'; + return ( + + {cron.name || cron.metadata?.name} + { + jobs.map((j: any) => j.targetSize || j.targetReplicas || '?').join(', ') + } + {jobs.map((j: any) => j.schedule || '?').join(', ')} + {tz} + + 已启用 + + + ); + })} + +
+
+ )} +
+
+
); } diff --git a/frontend/src/components/kubernetes/DeploymentWorkload.tsx b/frontend/src/components/kubernetes/DeploymentWorkload.tsx index 83257ce..4030a2c 100644 --- a/frontend/src/components/kubernetes/DeploymentWorkload.tsx +++ b/frontend/src/components/kubernetes/DeploymentWorkload.tsx @@ -302,25 +302,25 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
- {/* 模式切换 Tab — 三模式:查看 / 编辑 / YAML */} + {/* 模式切换 — 查看 / 编辑 / YAML */}
@@ -330,9 +330,6 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod {/* 操作按钮行 */}
-