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
@@ -12,6 +12,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { toast } from 'sonner';
|
||||
import { pipelineApi } from '@/api';
|
||||
import { FileBrowserDialog } from './FileBrowserDialog';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
// ═══════════ 阶段定义 (与 PipelineManager 共用) ═══════════
|
||||
const STAGE_DEFS = [
|
||||
@@ -61,7 +62,7 @@ const STATUS_BADGES: Record<string, string> = {
|
||||
cancelled: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
|
||||
};
|
||||
|
||||
const STATUS_ICONS: Record<string, any> = {
|
||||
const STATUS_ICONS: JsonData = {
|
||||
success: CheckCircle2,
|
||||
failed: XCircle,
|
||||
running: Loader2,
|
||||
@@ -101,8 +102,8 @@ function getRunStageStats(run: PipelineRun | null): Map<string, string> {
|
||||
// 如果 run.stages 有数据,直接用
|
||||
const statuses = parseStageStatuses(run.stages);
|
||||
statuses.forEach(s => {
|
||||
if (s.name || (s as Record<string, any>).type) {
|
||||
const key = (s as Record<string, any>).type || (STAGE_DEFS.find(d => d.label === s.name)?.key);
|
||||
if (s.name || (s as JsonData).type) {
|
||||
const key = (s as JsonData).type || (STAGE_DEFS.find(d => d.label === s.name)?.key);
|
||||
if (key) map.set(key, s.status);
|
||||
}
|
||||
});
|
||||
@@ -128,7 +129,7 @@ function getStageLogSummary(logs: LogEntry[], stageKey: string): string {
|
||||
|
||||
export default function PipelineRunDetail() {
|
||||
const { code: pipelineCode } = useParams<{ code: string }>();
|
||||
const [pipeline, setPipeline] = useState<any>(null);
|
||||
const [pipeline, setPipeline] = useState<JsonData>(null);
|
||||
const [runs, setRuns] = useState<PipelineRun[]>([]);
|
||||
const [selectedRun, setSelectedRun] = useState<PipelineRun | null>(null);
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
@@ -147,7 +148,7 @@ export default function PipelineRunDetail() {
|
||||
// 加载流水线详情
|
||||
const fetchPipeline = useCallback(async () => {
|
||||
try {
|
||||
const data: Record<string, any> = await pipelineApi.getByCode(pipelineCode);
|
||||
const data: JsonData = await pipelineApi.getByCode(pipelineCode);
|
||||
setPipeline(data?.data || data);
|
||||
} catch { /* silent, pipeline info optional */ }
|
||||
}, [pipelineCode]);
|
||||
@@ -156,7 +157,7 @@ export default function PipelineRunDetail() {
|
||||
const fetchRuns = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data: Record<string, any> = await pipelineApi.getRunsByCode(pipelineCode, pageSize);
|
||||
const data: JsonData = await pipelineApi.getRunsByCode(pipelineCode, pageSize);
|
||||
const list = data?.list || [];
|
||||
setRuns(list);
|
||||
if (!selectedRun && list.length > 0) {
|
||||
@@ -180,7 +181,7 @@ export default function PipelineRunDetail() {
|
||||
// 加载日志
|
||||
const fetchLogs = useCallback(async (run: PipelineRun) => {
|
||||
try {
|
||||
const data: Record<string, any> = await pipelineApi.getRunLogStream(run.id);
|
||||
const data: JsonData = await pipelineApi.getRunLogStream(run.id);
|
||||
if (data?.logs) {
|
||||
const parsed = typeof data.logs === 'string' ? JSON.parse(data.logs) : data.logs;
|
||||
setLogs(Array.isArray(parsed) ? parsed : []);
|
||||
|
||||
Reference in new issue
Block a user