Initial commit: SQL audit platform v0.1.0
FastAPI backend with auth, user, instance, ticket, query, dashboard APIs. React + TypeScript frontend with mock data UI (10 pages). SQLite + async SQLAlchemy, JWT auth, role-based access control. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
commit
439155c92e
290 files changed
+61950
No files matched your search
@@ -0,0 +1,134 @@
|
||||
import { useState } from 'react';
|
||||
import { Plus, Search, Edit2, Trash2, Shield, AlertTriangle, AlertCircle, Info, CheckCircle, ToggleLeft, ToggleRight, FileCode } from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { auditRules, dbTypeColorMap } from '@/lib/mock';
|
||||
|
||||
const levelConfig = {
|
||||
error: { icon: AlertTriangle, color: 'text-red-400 bg-red-400/10', label: '错误' },
|
||||
warning: { icon: AlertCircle, color: 'text-yellow-400 bg-yellow-400/10', label: '警告' },
|
||||
info: { icon: Info, color: 'text-blue-400 bg-blue-400/10', label: '提示' },
|
||||
};
|
||||
|
||||
export default function AuditRulesPage() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [levelFilter, setLevelFilter] = useState('');
|
||||
const [rules, setRules] = useState(auditRules);
|
||||
|
||||
const filtered = rules.filter(r => {
|
||||
if (levelFilter && r.level !== levelFilter) return false;
|
||||
if (search && !r.name.includes(search) && !r.description.includes(search)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const toggleRule = (id: string) => {
|
||||
setRules(prev => prev.map(r => r.id === id ? { ...r, enabled: !r.enabled } : r));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">SQL 审核规则</h1>
|
||||
<p className="text-sm text-[var(--dg-text-secondary)] mt-1">配置 SQL 审核规则,确保数据变更安全合规</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<button className="flex items-center gap-2 px-4 py-2.5 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm font-medium hover:bg-[var(--dg-bg-hover)] transition-all">
|
||||
<FileCode className="w-4 h-4" /> 导入规则
|
||||
</button>
|
||||
<button className="flex items-center gap-2 px-4 py-2.5 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg text-sm font-medium text-white hover:opacity-90 transition-opacity">
|
||||
<Plus className="w-4 h-4" /> 添加规则
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: '规则总数', value: rules.length, icon: Shield, color: 'text-blue-400', bg: 'bg-blue-400/10' },
|
||||
{ label: '已启用', value: rules.filter(r => r.enabled).length, icon: CheckCircle, color: 'text-green-400', bg: 'bg-green-400/10' },
|
||||
{ label: '错误级', value: rules.filter(r => r.level === 'error').length, icon: AlertTriangle, color: 'text-red-400', bg: 'bg-red-400/10' },
|
||||
{ label: '警告级', value: rules.filter(r => r.level === 'warning').length, icon: AlertCircle, color: 'text-yellow-400', bg: 'bg-yellow-400/10' },
|
||||
].map((s, i) => (
|
||||
<Card key={i} className="dg-card">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-lg ${s.bg} flex items-center justify-center`}><s.icon className={`w-5 h-5 ${s.color}`} /></div>
|
||||
<div><div className="text-2xl font-bold">{s.value}</div><div className="text-xs text-[var(--dg-text-muted)]">{s.label}</div></div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-4 dg-card flex-wrap">
|
||||
<div className="relative flex-1 max-w-xs">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--dg-text-muted)]" />
|
||||
<input className="w-full pl-10 pr-4 py-2.5 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm placeholder:text-[var(--dg-text-muted)] focus:border-blue-400 focus:outline-none" placeholder="搜索规则名称" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
<select className="px-3 py-2.5 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm focus:border-blue-400 focus:outline-none" value={levelFilter} onChange={e => setLevelFilter(e.target.value)}>
|
||||
<option value="">全部级别</option>
|
||||
<option value="error">错误</option>
|
||||
<option value="warning">警告</option>
|
||||
<option value="info">提示</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)]">
|
||||
<th className="text-left px-5 py-3 font-semibold">规则ID</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">规则名称</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">级别</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">适用数据库</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">描述</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">状态</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map(rule => {
|
||||
const lc = levelConfig[rule.level];
|
||||
const LIcon = lc.icon;
|
||||
return (
|
||||
<tr key={rule.id} className={`border-b border-[var(--dg-border-light)] hover:bg-[var(--dg-bg-hover)] transition-colors ${!rule.enabled ? 'opacity-50' : ''}`}>
|
||||
<td className="px-5 py-3.5 font-mono text-xs text-[var(--dg-text-secondary)]">{rule.id}</td>
|
||||
<td className="px-5 py-3.5 font-medium">{rule.name}</td>
|
||||
<td className="px-5 py-3.5">
|
||||
<span className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium ${lc.color}`}>
|
||||
<LIcon className="w-3.5 h-3.5" /> {lc.label}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-5 py-3.5">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{rule.dbTypes.map(dt => (
|
||||
<span key={dt} className={`inline-flex px-2 py-0.5 rounded text-[11px] font-medium ${dbTypeColorMap[dt] || ''}`}>{dt.toUpperCase()}</span>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-5 py-3.5 text-[var(--dg-text-secondary)] max-w-xs truncate">{rule.description}</td>
|
||||
<td className="px-5 py-3.5">
|
||||
<button onClick={() => toggleRule(rule.id)} className="transition-colors">
|
||||
{rule.enabled ? (
|
||||
<ToggleRight className="w-8 h-8 text-green-400" />
|
||||
) : (
|
||||
<ToggleLeft className="w-8 h-8 text-[var(--dg-text-muted)]" />
|
||||
)}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-5 py-3.5">
|
||||
<div className="flex gap-2">
|
||||
<button className="text-blue-400 hover:text-blue-300"><Edit2 className="w-4 h-4" /></button>
|
||||
<button className="text-red-400 hover:text-red-300"><Trash2 className="w-4 h-4" /></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user