feat(alert): A2 告警引擎分布式安全 — Redis 分布式锁(SetNX)

- Cache 接口新增 SetNX 方法(go-redis 原子 / redka check-then-set 单副本安全)
- AlertEngine 改造为支持分布式锁: tryAcquireLock/releaseLock/GetEngineStatus
- 多副本部署时仅 Leader 执行告警检查, 60s TTL 防死锁, fail-open 容错
- 新增 GET /alerts/engine/status 监控端点(is_leader/instance_id/lock_holder)
- 修复 GrafanaDashboard.tsx Panel.options 类型不匹配导致构建中断
- 同步未提交的前端类型安全迁移(140+ 文件)
- ROADMAP/TDD_PLAN/PROJECT_EVALUATION 三文档同步更新

验证: go build 零错误, Docker 部署 healthy, 引擎状态端点正常,
锁竞争测试通过(非 Leader 跳过检查), 5/5 核心 API 回归通过
This commit is contained in:
attacker committed 2026-06-26 21:13:15 +08:00
1 parent 5ca0e6d83a
commit c68aef4444
150 files changed
+1038 -690

No files matched your search

@@ -20,6 +20,7 @@ import { YamlViewer } from './YamlViewer';
import { PodExecTerminal } from './PodExecTerminal';
import { PodFileBrowser } from './PodFileBrowser';
import { formatK8sTime, statusColor, STATUS_COLORS } from './k8s-yaml-utils';
import type { JsonData } from '@/types/common';
type WorkloadKind = 'StatefulSet' | 'DaemonSet';
type ViewMode = 'view' | 'yaml';
@@ -67,14 +68,14 @@ export default function WorkloadViewPage() {
const kind: WorkloadKind = location.pathname.includes('/DaemonSet/') ? 'DaemonSet' : 'StatefulSet';
const mode: ViewMode = params.mode === 'yaml' ? 'yaml' : 'view';
const [manifest, setManifest] = useState<any>(null);
const [pods, setPods] = useState<any[]>([]);
const [manifest, setManifest] = useState<JsonData>(null);
const [pods, setPods] = useState<JsonData[]>([]);
const [loading, setLoading] = useState(true);
const [scaleModal, setScaleModal] = useState(false);
const [scaleValue, setScaleValue] = useState(1);
const [actionLoading, setActionLoading] = useState(false);
const [terminalPod, setTerminalPod] = useState<any>(null);
const [fileBrowserPod, setFileBrowserPod] = useState<any>(null);
const [terminalPod, setTerminalPod] = useState<JsonData>(null);
const [fileBrowserPod, setFileBrowserPod] = useState<JsonData>(null);
const [yamlModal, setYamlModal] = useState(false);
const loadData = useCallback(async () => {
@@ -95,7 +96,7 @@ export default function WorkloadViewPage() {
// Filter pods by name prefix and namespace
const podList = Array.isArray(podsRes) ? podsRes : (podsRes?.list || podsRes?.data || []);
const filtered = podList.filter(( p: any) => {
const filtered = podList.filter((p: JsonData) => {
const podName = p.name || p.metadata?.name || '';
const podNs = p.namespace || p.namespace_name || '';
return podName.startsWith(name) && (!podNs || podNs === namespace);
@@ -384,7 +385,7 @@ export default function WorkloadViewPage() {
</TableRow>
</TableHeader>
<TableBody>
{vcts.map(( vct: any, idx: number) => {
{vcts.map((vct: JsonData, idx: number) => {
const accessModes = (vct.spec?.accessModes || []).join(', ');
const storageClass = vct.spec?.storageClassName || vct.spec?.storageClassName || '-';
const size = vct.spec?.resources?.requests?.storage || '-';
@@ -415,7 +416,7 @@ export default function WorkloadViewPage() {
<div className="mb-3">
<p className="text-[11px] text-slate-500 mb-2 uppercase tracking-wide">Init Containers</p>
<div className="space-y-2">
{initContainers.map(( c: any, idx: number) => (
{initContainers.map((c: JsonData, idx: number) => (
<ContainerInfo key={`init-${idx}`} c={c} />
))}
</div>
@@ -426,7 +427,7 @@ export default function WorkloadViewPage() {
) : (
<div className="space-y-3">
<p className="text-[11px] text-slate-500 mb-1 uppercase tracking-wide">Containers</p>
{containers.map(( c: any, idx: number) => (
{containers.map((c: JsonData, idx: number) => (
<ContainerInfo key={idx} c={c} />
))}
</div>
@@ -456,7 +457,7 @@ export default function WorkloadViewPage() {
<TableBody>
{pods.length === 0 ? (
<TableRow><TableCell colSpan={6} className="text-center text-slate-500 py-8">暂无 Pod</TableCell></TableRow>
) : pods.map(( pod: any) => (
) : pods.map((pod: JsonData) => (
<TableRow key={pod.name} className="border-slate-800 hover:bg-slate-800/30">
<TableCell className="font-medium text-slate-200">{pod.name}</TableCell>
<TableCell>
@@ -531,7 +532,7 @@ export default function WorkloadViewPage() {
clusterId={clusterCode}
namespace={namespace}
podName={terminalPod.name}
containers={terminalPod.containers?.map(( c: any) => c.name).filter(Boolean) || []}
containers={terminalPod.containers?.map((c: JsonData) => c.name).filter(Boolean) || []}
/>
)}
@@ -543,7 +544,7 @@ export default function WorkloadViewPage() {
clusterCode={clusterCode}
namespace={namespace}
podName={fileBrowserPod.name}
containers={fileBrowserPod.containers?.map(( c: any) => c.name).filter(Boolean) || []}
containers={fileBrowserPod.containers?.map((c: JsonData) => c.name).filter(Boolean) || []}
/>
)}
</div>
@@ -551,7 +552,7 @@ export default function WorkloadViewPage() {
}
// Container info sub-component
function ContainerInfo({ c }: { c: any}) {
function ContainerInfo({ c }: { c: JsonData}) {
return (
<div className="border border-slate-800 rounded-lg p-3 bg-slate-950/30">
<div className="flex items-center justify-between mb-2">
@@ -570,7 +571,7 @@ function ContainerInfo({ c }: { c: any}) {
</div>
<div className="text-slate-400">
端口: <span className="text-slate-200 font-mono">
{(c.ports || []).map(( p: any) => p.containerPort || p.port).join(', ') || '-'}
{(c.ports || []).map((p: JsonData) => p.containerPort || p.port).join(', ') || '-'}
</span>
</div>
</div>