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

+1
View File
@@ -0,0 +1 @@
import{_ as m}from"./index-DfyorCky.js";function y(n){if(typeof document>"u")return;let a=document.head||document.getElementsByTagName("head")[0],t=document.createElement("style");t.type="text/css",a.appendChild(t),t.styleSheet?t.styleSheet.cssText=n:t.appendChild(document.createTextNode(n))}Array(12).fill(0);let b=1;class v{constructor(){this.subscribe=a=>(this.subscribers.push(a),()=>{const t=this.subscribers.indexOf(a);this.subscribers.splice(t,1)}),this.publish=a=>{this.subscribers.forEach(t=>t(a))},this.addToast=a=>{this.publish(a),this.toasts=[...this.toasts,a]},this.create=a=>{var t;const{message:e,...h}=a,s=typeof a?.id=="number"||((t=a.id)==null?void 0:t.length)>0?a.id:b++,c=this.toasts.find(d=>d.id===s),p=a.dismissible===void 0?!0:a.dismissible;return this.dismissedToasts.has(s)&&this.dismissedToasts.delete(s),c?this.toasts=this.toasts.map(d=>d.id===s?(this.publish({...d,...a,id:s,title:e}),{...d,...a,id:s,dismissible:p,title:e}):d):this.addToast({title:e,...h,dismissible:p,id:s}),s},this.dismiss=a=>(a?(this.dismissedToasts.add(a),requestAnimationFrame(()=>this.subscribers.forEach(t=>t({id:a,dismiss:!0})))):this.toasts.forEach(t=>{this.subscribers.forEach(e=>e({id:t.id,dismiss:!0}))}),a),this.message=(a,t)=>this.create({...t,message:a}),this.error=(a,t)=>this.create({...t,message:a,type:"error"}),this.success=(a,t)=>this.create({...t,type:"success",message:a}),this.info=(a,t)=>this.create({...t,type:"info",message:a}),this.warning=(a,t)=>this.create({...t,type:"warning",message:a}),this.loading=(a,t)=>this.create({...t,type:"loading",message:a}),this.promise=(a,t)=>{if(!t)return;let e;t.loading!==void 0&&(e=this.create({...t,promise:a,type:"loading",message:t.loading,description:typeof t.description!="function"?t.description:void 0}));const h=Promise.resolve(a instanceof Function?a():a);let s=e!==void 0,c;const p=h.then(async r=>{if(c=["resolve",r],m.isValidElement(r))s=!1,this.create({id:e,type:"default",message:r});else if(w(r)&&!r.ok){s=!1;const o=typeof t.error=="function"?await t.error(`HTTP error! status: ${r.status}`):t.error,f=typeof t.description=="function"?await t.description(`HTTP error! status: ${r.status}`):t.description,u=typeof o=="object"&&!m.isValidElement(o)?o:{message:o};this.create({id:e,type:"error",description:f,...u})}else if(r instanceof Error){s=!1;const o=typeof t.error=="function"?await t.error(r):t.error,f=typeof t.description=="function"?await t.description(r):t.description,u=typeof o=="object"&&!m.isValidElement(o)?o:{message:o};this.create({id:e,type:"error",description:f,...u})}else if(t.success!==void 0){s=!1;const o=typeof t.success=="function"?await t.success(r):t.success,f=typeof t.description=="function"?await t.description(r):t.description,u=typeof o=="object"&&!m.isValidElement(o)?o:{message:o};this.create({id:e,type:"success",description:f,...u})}}).catch(async r=>{if(c=["reject",r],t.error!==void 0){s=!1;const l=typeof t.error=="function"?await t.error(r):t.error,o=typeof t.description=="function"?await t.description(r):t.description,g=typeof l=="object"&&!m.isValidElement(l)?l:{message:l};this.create({id:e,type:"error",description:o,...g})}}).finally(()=>{s&&(this.dismiss(e),e=void 0),t.finally==null||t.finally.call(t)}),d=()=>new Promise((r,l)=>p.then(()=>c[0]==="reject"?l(c[1]):r(c[1])).catch(l));return typeof e!="string"&&typeof e!="number"?{unwrap:d}:Object.assign(e,{unwrap:d})},this.custom=(a,t)=>{const e=t?.id||b++;return this.create({jsx:a(e),id:e,...t}),e},this.getActiveToasts=()=>this.toasts.filter(a=>!this.dismissedToasts.has(a.id)),this.subscribers=[],this.toasts=[],this.dismissedToasts=new Set}}const i=new v,x=(n,a)=>{const t=a?.id||b++;return i.addToast({title:n,...a,id:t}),t},w=n=>n&&typeof n=="object"&&"ok"in n&&typeof n.ok=="boolean"&&"status"in n&&typeof n.status=="number",k=x,E=()=>i.toasts,T=()=>i.getActiveToasts(),S=Object.assign(k,{success:i.success,info:i.info,warning:i.warning,error:i.error,custom:i.custom,message:i.message,promise:i.promise,dismiss:i.dismiss,loading:i.loading},{getHistory:E,getToasts:T});y("[data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%)Line truncated