feat(audit): F1+F2 告警静默规则标记完成 + 审计日志CSV导出实现

F1 告警静默规则 [done]:
- 后端 alert_silence.go: CRUD + Toggle (onetime/daily/weekly 三模式)
- 告警引擎 isSilenced 集成 (alert_engine.go)
- 前端 SilenceRuleTab + SilenceDialog
- 路由 /alerts/silences 已注册

F2 审计日志导出 [done]:
- 重写 ExportAuditLogs: 真正的 CSV 导出 (encoding/csv + UTF-8 BOM)
- 支持 module/action/username/startTime/endTime 筛选
- 导出限制 10000 条防溢出,自动记录审计日志
- JSON 格式向后兼容 (format=json)
- 前端 exportLogs 传递查询参数

同时包含累积的前期工作:
- 目录重构 internal/ -> controller/model/router (v0.55-v0.57)
- 后端分层架构 dto/repository/service/utils
- 审计模块全功能 (终端审计/操作审计/登录日志)
- asciicast v2 录制格式
- SFTP 文件浏览器
- StatStrip 紧凑统计条
- 在线会话自动刷新
- LiveMonitor WebSocket 诊断修复

验证: go build ✅ | npm run build ✅ | Docker 部署 ✅ | 10 回归端点 ✅ | CSV 导出 ✅ | 静默 CRUD ✅
This commit is contained in:
attacker committed 2026-06-25 20:16:30 +08:00
1 parent e57718998a
commit 77c234b060
314 files changed
+10050 -4065

No files matched your search

@@ -0,0 +1,10 @@
import{r as c,j as e,s as V,B as A,al as oe,aj as Re,af as Pe,ac as le,am as Te,ae as $,ag as Y,an as Ee,ah as X,ad as Ae,ai as Me,X as M,D as ue,b as de,c as Ie,d as ie,e as xe,I as B,f as Fe,W as Ge,H as Oe,L as Le,a5 as Be,a3 as ee,q as Ue,G as ze,S as $e,aK as We,aL as qe,aM as He,aN as Ve,aO as te,aP as O,U as Xe,E as Ye,v as Je,T as Ze}from"./index-DfyorCky.js";import{C as _,b as U,c as z,d as pe,a as R}from"./card-C55cvSHg.js";import{B as y}from"./badge-xNW8x0G1.js";import{k as w}from"./kubernetes-J8O09fK7.js";import{t as G}from"./index-a3ybMjbR.js";import{T as Qe,a as et,b as I,c as F}from"./tabs-CIh_yD4s.js";import{C as me}from"./circle-x-DK79Dhg2.js";import{R as tt,C as st,X as rt,Y as nt,T as at}from"./generateCategoricalChart-6v9BPbG2.js";import{A as ct,a as se}from"./AreaChart-B2zlWfdX.js";import{T as re}from"./textarea-NXY8u32k.js";import{L as E}from"./label-BK0h2apY.js";import{u as ot}from"./index-CWVNW_qu.js";import{C as lt}from"./circle-DOZr6_68.js";import{x as ut,a as dt}from"./xterm-DSqgeKvr.js";import{M as it,a as xt}from"./minimize-2-CiCQYRfn.js";import{R as H}from"./refresh-cw-DUB_tAG-.js";import{P as pt}from"./plus-CUG-9kpu.js";import{E as mt}from"./ellipsis-DU5enAwm.js";import{D as bt}from"./download-DoZfYs2h.js";import{T as ht}from"./trash-2-Dx32pOzT.js";function gt({cluster:n,onClose:x}){const[a,g]=c.useState([]),[s,u]=c.useState([]),[d,N]=c.useState([]),[k,C]=c.useState([]),[b,f]=c.useState([]),h=c.useMemo(()=>{if(!a||a.length===0)return{cpuPct:0,memPct:0};let t=0,i=0,m=0;for(const j of a){const D=j.cpu||0,o=j.cpu_usage_m?parseFloat(String(j.cpu_usage_m).replace("m","")):0,l=D>0?o/1e3/D*100:0,v=j.memory_mb||0,P=j.mem_usage_mi?parseFloat(String(j.mem_usage_mi).replace("Mi","").replace("Ki","")):0,q=j.mem_usage_mi?.includes?.("Ki")?P/1024:j.mem_usage_mi?.includes?.("Gi")?P*1024:P,L=v>0?q/v*100:0;(l>0||L>0)&&(t+=l,i+=L,m++)}return{cpuPct:m>0?t/m:0,memPct:m>0?i/m:0}},[a]),p=async t=>{try{const[i,m,j,D,o]=await Promise.all([w.getNodes(t).catch(()=>[]),w.getPods(t).catch(()=>[]),w.getDeployments(t).catch(()=>[]),w.getServices(t).catch(()=>[]),w.getIngresses(t).catch(()=>[])]),l=v=>Array.isArray(v)?v:v?.list||[];g(l(i)),u(l(m)),N(l(j)),C(l(D)),f(l(o))}catch(i){console.error("加载集群详情失败:",i)}};return c.useEffect(()=>{n?.id&&p(n.id)},[n?.id]),e.jsxs(_,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:81:5",className:"bg-slate-900 border-slate-800",children:[e.jsx(U,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:82:7",children:e.jsxs("div",{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:83:9",className:"flex items-center justify-between",children:[e.jsxs("div",{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:84:11",className:"flex items-center gap-3",children:[e.jsx(V,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:85:13",className:"h-6 w-6 text-blue-400"}),e.jsxs("div",{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:86:13",children:[e.jsx(z,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:87:15",className:"text-slate-100",children:n.name}),e.jsxs(pe,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:88:15",className:"text-slate-500",children:[n.version," · ",n.node_count||0," 节点 · 创建于"," ",n.created_at?new Date(n.created_at).toLocaleDateString():"-"]})]})]}),e.jsxs(A,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:94:11",variant:"ghost",size:"sm",className:"text-slate-400 hover:text-slate-100",onClick:x,children:[e.jsx(me,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:100:13",className:"h-4 w-4 mr-2"}),"关闭"]})]})}),e.jsx(R,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:105:7",children:e.jsxs(Qe,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:106:9",defaultValue:"overview",className:"w-full",children:[e.jsxs(et,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:107:11",className:"bg-slate-800 border border-slate-700 flex-wrap h-auto",children:[e.jsx(I,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:108:13",value:"overview",className:"data-[state=active]:bg-slate-700",children:"概览"}),e.jsxs(I,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:111:13",value:"nodes",className:"data-[state=active]:bg-slate-700",children:["节点 (",a.length,")"]}),e.jsxs(I,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:114:13",value:"pods",className:"data-[state=active]:bg-slate-700",children:["Pod (",s.length,")"]}),e.jsxs(I,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:117:13",value:"deployments",className:"data-[state=active]:bg-slate-700",children:["Deployment (",d.length,")"]}),e.jsxs(I,{"code-path":"src/components/kubernetes/KubernetesClusterDetail.tsx:120:13",value:"services",className:"data-[state=active]:bg-slate-700",children:["Service (",kLine truncated
kind: Config
clusters:
- cluster:
server: https://10.0.0.1:6443
...`,rows:10,className:"bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] font-mono text-xs"}),!b&&e.jsx("p",{"code-path":"src/components/kubernetes/KubernetesClusterDialog.tsx:243:17",className:"text-xs text-[var(--dg-text-muted)]",children:"粘贴 kubeconfig 内容后将自动解析 API Server 地址"})]})]}),e.jsxs(Fe,{"code-path":"src/components/kubernetes/KubernetesClusterDialog.tsx:250:9",children:[e.jsx(A,{"code-path":"src/components/kubernetes/KubernetesClusterDialog.tsx:251:11",variant:"outline",onClick:()=>{x(!1)},className:"border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)]",children:"取消"}),e.jsx(A,{"code-path":"src/components/kubernetes/KubernetesClusterDialog.tsx:258:11",onClick:j,disabled:D,className:"bg-[var(--dg-blue)] hover:opacity-90 text-white",children:d?"保存中...":a?"保存":"添加"})]})]})})}function Tt({open:n,onOpenChange:x,clusterId:a,clusterName:g}){const s=c.useRef(null),u=c.useRef(null),d=c.useRef(null),N=c.useRef(null),[k,C]=c.useState(!1),[b,f]=c.useState(!1),[h,p]=c.useState("");return c.useEffect(()=>{n||f(!1)},[n]),c.useEffect(()=>{if(!n||!a)return;const t=new ut.Terminal({cursorBlink:!0,fontSize:14,fontFamily:'Menlo, Monaco, "Courier New", monospace',allowProposedApi:!0,theme:{background:"#0f172a",foreground:"#e2e8f0",cursor:"#38bdf8",selectionBackground:"#1e40af",black:"#1e293b",red:"#ef4444",green:"#10b981",yellow:"#f59e0b",blue:"#3b82f6",magenta:"#a855f7",cyan:"#06b6d4",white:"#e2e8f0",brightBlack:"#475569",brightRed:"#f87171",brightGreen:"#34d399",brightYellow:"#fbbf24",brightBlue:"#60a5fa",brightMagenta:"#c084fc",brightCyan:"#22d3ee",brightWhite:"#f8fafc"}}),i=new dt.FitAddon;t.loadAddon(i),u.current=t,N.current=i;const j=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}`,D=Ge.getState().token||"",o=`${j.replace(/^http/,"ws").replace("/api/v1","")}/ws/kubectl?token=${encodeURIComponent(D)}&id=${a}`,l=new WebSocket(o);return d.current=l,t.onData(v=>{l.readyState===WebSocket.OPEN&&l.send(JSON.stringify({status:0,data:v}))}),l.onmessage=v=>{try{const P=JSON.parse(v.data);P.status===0&&typeof P.data=="string"&&t.write(P.data)}catch{typeof v.data=="string"&&t.write(v.data)}},l.onopen=()=>{C(!0),p(""),s.current&&(t.open(s.current),requestAnimationFrame(()=>{try{i.fit()}catch{}}),setTimeout(()=>{try{t.focus()}catch{}},150))},l.onclose=()=>{C(!1),t.write(`\r
\x1B[31m[连接已断开]\x1B[0m\r
`)},l.onerror=()=>{p("WebSocket 连接失败"),t.write(`\r
\x1B[31m[连接发生错误]\x1B[0m\r
`)},()=>{try{(l.readyState===WebSocket.OPEN||l.readyState===WebSocket.CONNECTING)&&l.close(),t.dispose()}catch{}u.current=null,d.current=null,C(!1)}},[n,a]),c.useEffect(()=>{if(!n)return;const t=s.current;if(!t)return;let i;const m=new ResizeObserver(()=>{clearTimeout(i),i=setTimeout(()=>{if(N.current&&u.current&&d.current)try{N.current.fit(),u.current.focus();const{cols:j,rows:D}=u.current;d.current.readyState===WebSocket.OPEN&&d.current.send(JSON.stringify({status:1,cols:j,rows:D}))}catch{}},60)});return m.observe(t),()=>{clearTimeout(i),m.disconnect()}},[n]),e.jsx(ue,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:161:5",open:n,onOpenChange:x,children:e.jsxs(de,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:162:7",showCloseButton:!1,onOpenAutoFocus:t=>t.preventDefault(),className:M("bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] p-0 gap-0","!duration-0 !transition-none",b?"!max-w-none !w-screen !h-screen !rounded-none !translate-x-0 !translate-y-0 !left-0 !top-0":""),style:b?{maxWidth:"100vw",width:"100vw",height:"100vh"}:{maxWidth:"900px",width:"90vw"},children:[e.jsxs(ie,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:174:9",className:"sr-only",children:["kubectl — ",g]}),e.jsx(xe,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:175:9",className:"sr-only",children:"Kubernetes 集群命令行终端"}),e.jsxs("div",{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:178:9",className:"flex items-center justify-between px-4 py-2 border-b border-[var(--dg-border)]",children:[e.jsxs("div",{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:179:11",className:"flex items-center gap-2 text-sm",children:[e.jsx("div",{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:180:13",className:M("w-2 h-2 rounded-full",k?"bg-emerald-400":h?"bg-red-400":"bg-yellow-400")}),e.jsx(Oe,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:181:13",className:"h-4 w-4 text-blue-400"}),e.jsxs("span",{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:182:13",className:"text-[var(--dg-text-primary)] font-medium",children:["kubectl — ",g]}),!k&&!h&&e.jsx(Le,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:183:38",className:"h-3 w-3 animate-spin text-[var(--dg-text-muted)]"})]}),e.jsxs("div",{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:185:11",className:"flex items-center gap-1",children:[e.jsx("button",{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:186:13",onClick:()=>f(!b),className:"p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]",children:b?e.jsx(it,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:190:29",className:"h-4 w-4"}):e.jsx(xt,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:190:65",className:"h-4 w-4"})}),e.jsx("button",{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:192:13",onClick:()=>x(!1),className:"p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]",children:e.jsx(Be,{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:196:15",className:"h-4 w-4"})})]})]}),e.jsx("div",{"code-path":"src/components/kubernetes/KubectlTerminal.tsx:202:9",ref:s,className:"bg-[#0f172a]",style:{height:b?"calc(100vh - 49px)":"480px"}})]})})}function Qt(){const[n,x]=c.useState([]),[a,g]=c.useState(!0),[s,u]=c.useState(null),[d,N]=c.useState(""),[k,C]=c.useState(!1),[b,f]=c.useState(null),[h,p]=c.useState(null),[t,i]=c.useState(!1),[m,j]=c.useState(null),[D,o]=c.useState(!1),l=async()=>{try{g(!0);const r=await w.getClusters();x(r?.list||[])}catch(r){console.error("加载集群列表失败:",r),x([])}finally{g(!1)}};c.useEffect(()=>{l()},[]);const[,v]=c.useState(null),P=()=>{l().then(()=>{b||setTimeout(async()=>{const K=(await w.getClusters())?.list||[];K.length>0&&L(K[K.length-1].id,!0)},500)})},q=async r=>{try{await w.deleteCluster(r),l()}catch(K){console.error("删除集群失败:",K)}},L=async(r,K)=>{try{K||G.info("正在同步集群资源...");const T=await w.syncCluster(r);K||G.success(T?.message||"同步完成"),l()}catch(T){K||G.error(T?.message||"同步失败")}},we=async()=>{if(!m)return;const r=m.code||String(m.id);try{o(!0),G.info(`正在导出 ${m.name} 所有命名空间的资源...`),await w.exportAllResources(r),G.success("导出完成,已开始下载"),j(null)}catch(K){G.error(K?.message||"导出失败")}finally{o(!1)}},Z=n.filter(r=>(r.name||"").toLowerCase().includes(d.toLowerCase())||(r.version||"").includes(d)),Se=r=>{switch(r){case"healthy":return e.jsxs(y,{"code-path":"src/components/kubernetes/KubernetesClusters.tsx:138:11",variant:"outline",className:"border-emerald-500/30 text-emerald-400 bg-emerald-500/10",children:[e.jsx(ee,{"code-path":"src/components/kubernetes/KubernetesClusters.tsx:139:13",className:"h-3 w-3 mr-1"}),"健康"]});case"warning":return e.jsxs(y,{"code-path":"src/components/kubernetes/KubernetesClusters.tsx:145:11",variant:"outline",className:"border-amber-500/30 text-amb Line truncated