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 @@
import{g as Ne,r as o,a as ve,j as e,L as M,t as ae,B as a,a0 as D,I as h,a9 as fe,a5 as le,aa as we,u as be}from"./index-DfyorCky.js";import{C as g,a as N}from"./card-C55cvSHg.js";import{B as x}from"./badge-xNW8x0G1.js";import{S as v,a as f,b as w,c as b,d as r}from"./select-D5qUzdAs.js";import{L as i}from"./label-BK0h2apY.js";import{t as c}from"./index-a3ybMjbR.js";import{d as m,p as Se}from"./deploy-B3Sg3N1U.js";import{P as pe}from"./plus-CUG-9kpu.js";import{S as ne}from"./save-DoGGjfwC.js";import{T as ke}from"./trash-2-Dx32pOzT.js";import{U as de}from"./user-plus-BwUWtcQl.js";import"./index-CWVNW_qu.js";const Ie=[["path",{d:"M17 3v10",key:"15fgeh"}],["path",{d:"m12.67 5.5 8.66 5",key:"1gpheq"}],["path",{d:"m12.67 10.5 8.66-5",key:"1dkfa6"}],["path",{d:"M9 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2z",key:"swwfx4"}]],re=Ne("regex",Ie);function He(){const[p,me]=o.useState([]),[xe,T]=o.useState(!0),[S,A]=o.useState(!1),[y,ie]=o.useState([]),[n,k]=o.useState(null),[F,ye]=o.useState([]),[I,_]=o.useState([]),[l,he]=o.useState(""),[j,q]=o.useState(""),[G,H]=o.useState(!1),[J,z]=o.useState(!1),[P,X]=o.useState(""),[E,K]=o.useState(""),[C,O]=o.useState(""),[Q,W]=o.useState("editor"),[Y,Z]=o.useState(""),[Ce,U]=o.useState(null),[L,ee]=o.useState(""),[se,te]=o.useState(""),Re=ve(),R=async()=>{T(!0);try{const[s,t,d]=await Promise.all([m.getItemRoles(),Se.getPipelines({page_size:200}),be.getUsers({page_size:500})]);me(Array.isArray(s)?s:s?.list||s?.data||[]),ie(Array.isArray(t)?t:t?.list||t?.data||[]);const u=Array.isArray(d)?d:d?.list||d?.data||[];ye(u)}catch{}finally{T(!1)}};o.useEffect(()=>{R()},[]);const $=async()=>{if(!l){_([]);return}try{const s=await m.getUserItemRoles(Number(l));_(Array.isArray(s)?s:s?.data||s?.list||[])}catch{_([])}};o.useEffect(()=>{$()},[l]);const B=o.useMemo(()=>{const s={};return p.forEach(t=>{try{const d=new RegExp(t.pattern);s[t.id]=y.filter(u=>d.test(u.code)).map(u=>u.code)}catch{s[t.id]=[]}}),s},[p,y]),oe=(n?p.find(s=>s.id===n):null)?B[n]||[]:[],ue=async()=>{if(!P.trim()||!E.trim()||!C.trim()){c.error("名称、代码、正则表达式不能为空");return}A(!0);try{await m.createItemRole({name:P.trim(),code:E.trim(),pattern:C.trim(),role:Q,description:Y}),c.success("已创建"),z(!1),X(""),K(""),O(""),W("editor"),Z(""),R()}catch(s){c.error(s?.message||"创建失败")}finally{A(!1)}},Ae=async s=>{if(!L.trim()){c.error("正则不能为空");return}A(!0);try{await m.updateItemRole(s,{pattern:L,role:se}),c.success("已更新"),U(null),R()}catch(t){c.error(t?.message||"更新失败")}finally{A(!1)}},je=async s=>{if(await Re({title:"删除角色",description:`确定删除「${s.name}」?匹配 ${B[s.id]?.length||0} 条流水线`,variant:"destructive"}))try{await m.deleteItemRole(s.id),c.success("已删除"),R()}catch{c.error("删除失败")}},Be=async s=>{try{await m.updateItemRole(s.id,{status:s.status?0:1}),c.success(s.status?"已禁用":"已启用"),R()}catch{c.error("操作失败")}},De=async()=>{if(!l||!j){c.error("请选择用户和角色");return}H(!0);try{await m.assignItemRole(Number(l),{user_id:Number(l),item_role_id:Number(j)}),c.success("已分配"),q(""),$()}catch(s){c.error(s?.message||"分配失败")}finally{H(!1)}},ge=async s=>{try{await m.removeItemRole(Number(l),s),c.success("已撤销"),$()}catch{c.error("撤销失败")}},ce=s=>s==="owner"?"bg-amber-500/20 text-amber-400 border-amber-500/30":s==="editor"?"bg-blue-500/20 text-blue-400 border-blue-500/30":"bg-slate-500/20 text-slate-400 border-slate-500/30",V=F.find(s=>String(s.id)===l);return xe?e.jsx("div",{"code-path":"src/components/deploy/DeployRBAC.tsx:167:23",className:"flex items-center justify-center py-20",children:e.jsx(M,{"code-path":"src/components/deploy/DeployRBAC.tsx:167:79",className:"w-6 h-6 animate-spin"})}):e.jsxs("div",{"code-path":"src/components/deploy/DeployRBAC.tsx:170:5",className:"space-y-4",children:[e.jsxs("div",{"code-path":"src/components/deploy/DeployRBAC.tsx:172:7",className:"flex items-center justify-between",children:[e.jsxs("div",{"code-path":"src/components/deploy/DeployRBAC.tsx:173:9",children:[e.jsxs("h1",{"code-path":"src/components/deploy/DeployRBAC.tsx:174:11",className:"text-xl font-bold flex items-center gap-2 text-[var(--dg-text-primary)]",children:[e.jsx(ae,{"code-path":"src/components/deploy/DeployRBAC.tsx:175:13",className:"w-5 h-5 text-purple-500"}),"发布权限"]}),e.jsx("p",{"code-path":"src/components/deploy/DeployRBAC.tsx:177:11",className:"text-sm text-[var(--dg-text-secondary)] mt-1",children:"Jenkins-style Role Strategy — 按流水线 code 正则匹配分配角色"})]}),e.jsxs(a,{"code-path":"src/components/deploy/DeployRBAC.tsx:181:9",onClick:()=>z(!0),disabled:J,children:[e.jsx(pe,{"code-path":"src/components/deploy/DeployRBAC.tsx:182:11",className:"w-4 h-4 mr-1"}),"新建角色"]})]}),e.jsx(g,{"code-path":"src/components/deploy/DeployRBAC.tsx:187:7",className:"bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]",children:e.jsxs(N,{Line truncated