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:
1 parent
5ca0e6d83a
commit
c68aef4444
150 files changed
+1038
-690
No files matched your search
@@ -3,10 +3,11 @@
|
||||
import type {
|
||||
ContainerForm, ProbeDef, LifecycleHandler,
|
||||
} from './types';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
// K8s container spec → form conversion // Logan.Li
|
||||
export function toContainerForm(c: any): ContainerForm {
|
||||
const probe = (p: any): ProbeDef | null => {
|
||||
export function toContainerForm(c: JsonData): ContainerForm {
|
||||
const probe = (p: JsonData): ProbeDef | null => {
|
||||
if (!p) return null;
|
||||
let type: ProbeDef['type'] = 'none';
|
||||
if (p.httpGet) type = 'httpGet';
|
||||
@@ -26,7 +27,7 @@ export function toContainerForm(c: any): ContainerForm {
|
||||
failureThreshold: p.failureThreshold,
|
||||
};
|
||||
};
|
||||
const lh = (h: any): LifecycleHandler => {
|
||||
const lh = (h: JsonData): LifecycleHandler => {
|
||||
if (!h) return { type: 'none' };
|
||||
let type: LifecycleHandler['type'] = 'none';
|
||||
if (h.exec) type = 'exec';
|
||||
@@ -48,8 +49,8 @@ export function toContainerForm(c: any): ContainerForm {
|
||||
command: Array.isArray(c.command) ? c.command : [],
|
||||
args: Array.isArray(c.args) ? c.args : [],
|
||||
workingDir: c.workingDir || '',
|
||||
env: (Array.isArray(c.env) ? c.env : []).map((e: any) => ({ name: e.name || '', value: e.value ?? '' })),
|
||||
ports: (Array.isArray(c.ports) ? c.ports : []).map((p: any) => ({ name: p.name || '', containerPort: p.containerPort ?? '', protocol: p.protocol || 'TCP' })),
|
||||
env: (Array.isArray(c.env) ? c.env : []).map((e: JsonData) => ({ name: e.name || '', value: e.value ?? '' })),
|
||||
ports: (Array.isArray(c.ports) ? c.ports : []).map((p: JsonData) => ({ name: p.name || '', containerPort: p.containerPort ?? '', protocol: p.protocol || 'TCP' })),
|
||||
resources: {
|
||||
requests: c.resources?.requests || {},
|
||||
limits: c.resources?.limits || {},
|
||||
@@ -71,7 +72,7 @@ export function toContainerForm(c: any): ContainerForm {
|
||||
capabilitiesAdd: sc.capabilities?.add || [],
|
||||
capabilitiesDrop: sc.capabilities?.drop || [],
|
||||
} : null,
|
||||
volumeMounts: (Array.isArray(c.volumeMounts) ? c.volumeMounts : []).map((m: any) => ({
|
||||
volumeMounts: (Array.isArray(c.volumeMounts) ? c.volumeMounts : []).map((m: JsonData) => ({
|
||||
name: m.name || '', mountPath: m.mountPath || '', subPath: m.subPath || '', readOnly: m.readOnly || false,
|
||||
})),
|
||||
};
|
||||
@@ -88,9 +89,9 @@ export function emptyContainer(name = ''): ContainerForm {
|
||||
};
|
||||
}
|
||||
|
||||
export function probeToK8s(p: ProbeDef | null | undefined): any {
|
||||
export function probeToK8s(p: ProbeDef | null | undefined): JsonData {
|
||||
if (!p || p.type === 'none' || !p.type) return undefined;
|
||||
const out: Record<string, any> = {};
|
||||
const out: JsonData = {};
|
||||
if (p.type === 'httpGet') {
|
||||
out.httpGet = {};
|
||||
if (p.httpPath) out.httpGet.path = p.httpPath;
|
||||
@@ -111,28 +112,28 @@ export function probeToK8s(p: ProbeDef | null | undefined): any {
|
||||
return Object.keys(out).length > 0 ? out : undefined;
|
||||
}
|
||||
|
||||
export function handlerToK8s(h: LifecycleHandler | undefined): any {
|
||||
export function handlerToK8s(h: LifecycleHandler | undefined): JsonData {
|
||||
if (!h || h.type === 'none' || !h.type) return undefined;
|
||||
if (h.type === 'exec') {
|
||||
const cmds = (h.execCommand || '').split('\n').map(s => s.trim()).filter(Boolean);
|
||||
return cmds.length ? { exec: { command: cmds } } : undefined;
|
||||
}
|
||||
if (h.type === 'httpGet') {
|
||||
const o: Record<string, any> = { httpGet: {} };
|
||||
const o: JsonData = { httpGet: {} };
|
||||
if (h.httpPath) o.httpGet.path = h.httpPath;
|
||||
if (h.httpPort !== '' && h.httpPort != null) o.httpGet.port = Number(h.httpPort);
|
||||
return o;
|
||||
}
|
||||
if (h.type === 'tcpSocket') {
|
||||
const o: Record<string, any> = { tcpSocket: {} };
|
||||
const o: JsonData = { tcpSocket: {} };
|
||||
if (h.tcpPort !== '' && h.tcpPort != null) o.tcpSocket.port = Number(h.tcpPort);
|
||||
return o;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function containerToK8s(c: ContainerForm): any {
|
||||
const out: Record<string, any> = { name: c.name };
|
||||
export function containerToK8s(c: ContainerForm): JsonData {
|
||||
const out: JsonData = { name: c.name };
|
||||
if (c.image) out.image = c.image;
|
||||
if (c.imagePullPolicy) out.imagePullPolicy = c.imagePullPolicy;
|
||||
if (c.workingDir) out.workingDir = c.workingDir;
|
||||
@@ -140,14 +141,14 @@ export function containerToK8s(c: ContainerForm): any {
|
||||
if (c.args.length > 0) out.args = c.args;
|
||||
if (c.env.length > 0) {
|
||||
out.env = c.env.filter(e => e.name.trim()).map(e => {
|
||||
const o: Record<string, any> = { name: e.name };
|
||||
const o: JsonData = { name: e.name };
|
||||
if (e.value != null) o.value = e.value;
|
||||
return o;
|
||||
});
|
||||
}
|
||||
if (c.ports.length > 0) {
|
||||
out.ports = c.ports.filter(p => p.containerPort !== '').map(p => {
|
||||
const o: Record<string, any> = { containerPort: typeof p.containerPort === 'number' ? p.containerPort : Number(p.containerPort) };
|
||||
const o: JsonData = { containerPort: typeof p.containerPort === 'number' ? p.containerPort : Number(p.containerPort) };
|
||||
if (p.name) o.name = p.name;
|
||||
if (p.protocol) o.protocol = p.protocol;
|
||||
return o;
|
||||
@@ -174,14 +175,14 @@ export function containerToK8s(c: ContainerForm): any {
|
||||
const rp = probeToK8s(c.readinessProbe); if (rp) out.readinessProbe = rp;
|
||||
const sp = probeToK8s(c.startupProbe); if (sp) out.startupProbe = sp;
|
||||
if (c.lifecycle) {
|
||||
const lc: Record<string, any> = {};
|
||||
const lc: JsonData = {};
|
||||
const ps = handlerToK8s(c.lifecycle.postStart); if (ps) lc.postStart = ps;
|
||||
const pst = handlerToK8s(c.lifecycle.preStop); if (pst) lc.preStop = pst;
|
||||
if (Object.keys(lc).length > 0) out.lifecycle = lc;
|
||||
}
|
||||
if (c.securityContext) {
|
||||
const s = c.securityContext;
|
||||
const sc: Record<string, any> = {};
|
||||
const sc: JsonData = {};
|
||||
if (s.runAsUser !== '' && s.runAsUser != null) sc.runAsUser = Number(s.runAsUser);
|
||||
if (s.runAsGroup !== '' && s.runAsGroup != null) sc.runAsGroup = Number(s.runAsGroup);
|
||||
if (s.runAsNonRoot != null) sc.runAsNonRoot = s.runAsNonRoot;
|
||||
@@ -199,7 +200,7 @@ export function containerToK8s(c: ContainerForm): any {
|
||||
}
|
||||
if (c.volumeMounts.length > 0) {
|
||||
out.volumeMounts = c.volumeMounts.filter(m => m.name.trim() && m.mountPath.trim()).map(m => {
|
||||
const o: Record<string, any> = { name: m.name, mountPath: m.mountPath };
|
||||
const o: JsonData = { name: m.name, mountPath: m.mountPath };
|
||||
if (m.subPath) o.subPath = m.subPath;
|
||||
if (m.readOnly) o.readOnly = true;
|
||||
return o;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JsonData } from "@/types/common";
|
||||
// deployment-edit/types.ts — 容器编辑器类型定义
|
||||
// 作者: Logan <admin@attacker.club>
|
||||
|
||||
@@ -64,8 +65,8 @@ export interface DeploymentEditContainersProps {
|
||||
clusterCode: string;
|
||||
namespace: string;
|
||||
deploymentName: string;
|
||||
manifest: any | null;
|
||||
onSaved: (updated?: any) => void;
|
||||
manifest: JsonData | null;
|
||||
onSaved: (updated?: JsonData) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user