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

@@ -48,6 +48,9 @@ const ROUTE_NAME_MAP: Record<string, string> = {
'host-monitor': '主机监控',
'alerts': '监控告警',
'approval': '资源申请',
'approval-resource': '资源申请',
'approval-app': '应用申请',
'approval-permission': '权限审批',
'scripts': '脚本库',
'ansible': 'Ansible',
'ai-assistant': '智能助手',
+2 -104
View File
@@ -1,9 +1,8 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { useState, useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { cn } from '@/lib/utils';
import { useThemeStore, useNotificationStore, useUserStore } from '@/store';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import {
DropdownMenu,
@@ -14,7 +13,6 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Search,
Bell,
Menu,
Settings,
@@ -30,30 +28,18 @@ import {
import { formatDistanceToNow } from '@/lib/utils';
import { useTheme, THEME_OPTIONS } from '@/hooks/useTheme';
// Keyboard shortcut handler
// Keyboard shortcut handler — Cmd/Ctrl+B 切换侧边栏
function useKeyboardShortcuts() {
const _navigate = useNavigate();
const [searchOpen, setSearchOpen] = useState(false);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
// Cmd/Ctrl + K to open search
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
setSearchOpen((prev) => !prev);
}
// Cmd/Ctrl + B to toggle sidebar
if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
e.preventDefault();
// Dispatch custom event for sidebar toggle
window.dispatchEvent(new CustomEvent('toggle-sidebar'));
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, []);
return { searchOpen, setSearchOpen };
}
export function Header() {
@@ -62,9 +48,6 @@ export function Header() {
const { user, logout } = useUserStore();
const { theme, setTheme } = useTheme();
const navigate = useNavigate();
const [searchValue, setSearchValue] = useState('');
const [searchFocused, setSearchFocused] = useState(false);
const searchInputRef = useRef<HTMLInputElement>(null);
useKeyboardShortcuts();
const handleLogout = () => {
@@ -87,18 +70,6 @@ export function Header() {
}
}, []);
// Quick nav items for search
const quickNavItems = [
{ name: '仪表盘', path: '/', icon: 'LayoutDashboard' },
{ name: '服务器管理', path: '/servers', icon: 'Server' },
{ name: 'Kubernetes 集群', path: '/kubernetes', icon: 'Cloud' },
{ name: '监控告警', path: '/alerts', icon: 'Bell' },
{ name: '告警中心', path: '/alert-center', icon: 'AlertTriangle' },
{ name: '流水线', path: '/pipelines', icon: 'GitBranch' },
{ name: '智能助手', path: '/ai-assistant', icon: 'Brain' },
{ name: '脚本库', path: '/scripts', icon: 'FileCode' },
];
return (
<header
className={cn(
@@ -121,79 +92,6 @@ export function Header() {
>
<Menu className="h-4 w-4" />
</Button>
{/* Global Search */}
<div className={cn(
'relative transition-all duration-300',
searchFocused ? 'w-[28rem] max-w-full' : 'w-80'
)}>
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[var(--dg-text-muted)] pointer-events-none" />
<Input
ref={searchInputRef}
type="text"
placeholder="搜索服务器、应用、工单、文档..."
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onFocus={() => setSearchFocused(true)}
onBlur={() => setTimeout(() => setSearchFocused(false), 200)}
className={cn(
'pl-9 h-9 text-sm transition-all duration-300',
'bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]',
'text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]',
'focus:border-[var(--dg-blue)] focus:ring-1 focus:ring-[var(--dg-blue)]/30',
'hover:border-[var(--dg-border-hover)]',
)}
/>
<kbd className="absolute right-3 top-1/2 -translate-y-1/2 px-1.5 py-0.5 text-[10px] font-mono text-[var(--dg-text-muted)] bg-[var(--dg-bg-primary)] border border-[var(--dg-border)] rounded hidden sm:inline-block">
⌘K
</kbd>
{/* Search Dropdown */}
{searchFocused && searchValue && (
<div
className={cn(
'absolute top-full left-0 right-0 mt-2 py-2 rounded-lg',
'bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)]',
'shadow-lg z-50 animate-scale-in',
)}
style={{ boxShadow: 'var(--shadow-dropdown)' }}
>
<div className="px-3 py-1.5 text-xs font-medium text-[var(--dg-text-muted)] uppercase tracking-wider">
快速导航
</div>
{quickNavItems
.filter((item) =>
item.name.toLowerCase().includes(searchValue.toLowerCase())
)
.map((item) => (
<button
key={item.path}
className={cn(
'w-full flex items-center gap-2.5 px-3 py-2 text-sm',
'text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)]',
'hover:bg-[var(--dg-bg-hover)] transition-colors text-left',
)}
onMouseDown={(e) => {
e.preventDefault();
navigate(item.path);
setSearchValue('');
setSearchFocused(false);
}}
>
<Search className="h-3.5 w-3.5" />
<span>{item.name}</span>
</button>
))}
{quickNavItems.filter((item) =>
item.name.toLowerCase().includes(searchValue.toLowerCase())
).length === 0 && (
<div className="px-3 py-4 text-center text-sm text-[var(--dg-text-muted)]">
未找到匹配的结果
</div>
)}
</div>
)}
</div>
</div>
{/* Right: Actions */}
+18 -3
View File
@@ -35,6 +35,9 @@ import {
Boxes,
Clock,
Rocket,
ShieldCheck,
FileSearch,
LogIn,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
@@ -132,14 +135,26 @@ const menuItems: MenuItem[] = [
{ id: 'ai-predictive', name: '容量预测', icon: BarChart3, path: '/ai-predictive' },
],
},
{ id: 'approval', name: '资源申请', icon: CheckSquare, path: '/approval' },
{ id: 'approval', name: '资源申请', icon: CheckSquare,
children: [
{ id: 'approval-resource', name: '资源申请', icon: Server, path: '/approval/resource' },
{ id: 'approval-app', name: '应用申请', icon: Rocket, path: '/approval/app' },
{ id: 'approval-permission', name: '权限审批', icon: ShieldCheck, path: '/approval/permission' },
],
},
{
id: 'audit', name: '安全审计', icon: FileSearch,
children: [
{ id: 'audit-terminal', name: '终端审计', icon: Terminal, path: '/audit/terminal' },
{ id: 'audit-operations', name: '操作审计', icon: FileCode, path: '/audit/operations' },
{ id: 'audit-login', name: '登录日志', icon: LogIn, path: '/audit/login-logs' },
],
},
{
id: 'system', name: '系统管理', icon: Settings,
children: [
{ id: 'users', name: '用户管理', icon: Users, path: '/users' },
{ id: 'roles', name: '角色权限', icon: Shield, path: '/roles' },
{ id: 'logs-audit', name: '操作审计', icon: FileCode, path: '/logs-audit' },
{ id: 'terminal-audit', name: '终端审计', icon: Terminal, path: '/terminal-audit' },
{ id: 'credentials', name: '凭证配置', icon: Shield, path: '/credentials' },
{ id: 'settings', name: '系统设置', icon: Settings, path: '/settings' },
],