fix: 审核引擎优化 + 执行结果结构化展示

后端审核规则修复:
- Redis SEC-001: SET/DEL等常规命令不再误报AUTH要求,改为仅对管理级命令WARNING
- Redis SEC-003: 降级为INFO,排除已由CMD-001拦截的FLUSHDB/FLUSHALL
- Redis key提取: 排除CONFIG/ACL等管理命令,不再把子命令参数误认为key名
- MySQL SEC-006: 新增GRANT ALL/REVOKE ALL全局权限拦截
- MySQL SEC-007: 新增LOAD DATA/LOAD XML拦截
- MySQL SEC-008: 新增SLEEP()/BENCHMARK()时间盲注拦截
- MySQL SEC-009: 新增INTO OUTFILE/DUMPFILE文件写入拦截

前端执行结果结构化:
- TicketDetailPage执行结果从原始JSON改为结构化展示
- 解析JSON展示: 状态徽章+影响行数+耗时+验证状态
- Errors/Warnings逐条渲染: 红色错误框+黄色警告框+具体消息
- 删除废弃的executeStats/renderExecuteStats

其他: 审核规则6分组/慢查询移除/查询数据重命名/AuditLogs修复
测试: 1040 passed, TSC零错误
This commit is contained in:
logan committed 2026-05-24 01:31:33 +08:00
1 parent 9f56e866f2
commit 4ac2528f8e
19 files changed
+1202 -325

No files matched your search

+58 -32
View File
@@ -11,6 +11,7 @@ import { useActionPermission } from '@/hooks/useAuth';
import PageHeader from '@/components/PageHeader';
import EmptyState from '@/components/EmptyState';
import { instanceApi } from '@/api/instance';
import EditInstanceDialog from '@/components/EditInstanceDialog';
/* ------------------------------------------------------------------ */
/* Types */
@@ -41,11 +42,14 @@ type ViewMode = 'grid' | 'list';
/* ------------------------------------------------------------------ */
/* Constants */
/* ------------------------------------------------------------------ */
/* ------------------------------------------------------------------ */
/* DB Brand Icons */
const dbTypeConfig: Record<string, { icon: React.ElementType; color: string; bg: string; label: string }> = {
mysql: { icon: Database, color: 'text-[#26a69a]', bg: 'bg-[rgba(0,117,143,0.15)]', label: 'MySQL' },
postgres: { icon: Server, color: 'text-[#42a5f5]', bg: 'bg-[rgba(51,103,145,0.15)]', label: 'PostgreSQL' },
redis: { icon: Zap, color: 'text-[#ef5350]', bg: 'bg-[rgba(220,50,32,0.15)]', label: 'Redis' },
mongo: { icon: Leaf, color: 'text-[#66bb6a]', bg: 'bg-[rgba(71,162,72,0.15)]', label: 'MongoDB' },
mysql: { icon: MySQLIcon, color: 'text-[#26a69a]', bg: 'bg-[rgba(0,117,143,0.15)]', label: 'MySQL' },
postgres: { icon: PostgreSQLIcon, color: 'text-[#42a5f5]', bg: 'bg-[rgba(51,103,145,0.15)]', label: 'PostgreSQL' },
redis: { icon: RedisIcon, color: 'text-[#ef5350]', bg: 'bg-[rgba(220,50,32,0.15)]', label: 'Redis' },
mongo: { icon: MongoDBIcon, color: 'text-[#66bb6a]', bg: 'bg-[rgba(71,162,72,0.15)]', label: 'MongoDB' },
};
const envLabel: Record<string, string> = {
@@ -132,6 +136,8 @@ export default function InstancePage() {
const [instances, setInstances] = useState<InstanceItem[]>([]);
const [loading, setLoading] = useState(false);
const [showCreate, setShowCreate] = useState(false);
const [showEdit, setShowEdit] = useState(false);
const [editingInstance, setEditingInstance] = useState<InstanceItem | null>(null);
const [selectedIds, setSelectedIds] = useState<Set<number>>(new Set());
const [batchDeleting, setBatchDeleting] = useState(false);
@@ -242,6 +248,28 @@ export default function InstancePage() {
);
}
if (showEdit && editingInstance) {
return (
<>
<InstanceDetail
instance={editingInstance}
onBack={() => { setView('grid'); setShowEdit(false); setEditingInstance(null); }}
config={dbTypeConfig[editingInstance.db_type] || dbTypeConfig.mysql}
canEdit={canEdit}
canDelete={canDelete}
onDelete={() => { handleDelete(editingInstance.id); setView('grid'); setShowEdit(false); setEditingInstance(null); }}
onEdit={() => {}}
/>
<EditInstanceDialog
instance={editingInstance}
open={showEdit}
onClose={() => { setShowEdit(false); setEditingInstance(null); }}
onSaved={() => { setShowEdit(false); setEditingInstance(null); fetchInstances(); }}
/>
</>
);
}
const selectedConfig = selectedInstance
? (dbTypeConfig[selectedInstance.db_type] || dbTypeConfig.mysql)
: dbTypeConfig.mysql;
@@ -255,6 +283,7 @@ export default function InstancePage() {
canEdit={canEdit}
canDelete={canDelete}
onDelete={() => { handleDelete(selectedInstance.id); setView('grid'); }}
onEdit={() => { setEditingInstance(selectedInstance); setShowEdit(true); }}
/>
);
}
@@ -428,7 +457,7 @@ export default function InstancePage() {
) : filtered.length === 0 ? (
<EmptyState type="search" />
) : layoutMode === 'grid' ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{filtered.map(instance => {
const ic = dbTypeConfig[instance.db_type] || dbTypeConfig.mysql;
const IconComp = ic.icon;
@@ -438,7 +467,7 @@ export default function InstancePage() {
return (
<div
key={instance.id}
className={`dg-card p-5 cursor-pointer dg-card-hover group relative transition-all ${
className={`dg-card p-3.5 cursor-pointer dg-card-hover group relative transition-all ${
isSelected ? 'ring-2 ring-blue-500/50' : ''
}`}
onClick={() => { setSelectedInstance(instance); setView('detail'); }}
@@ -456,8 +485,8 @@ export default function InstancePage() {
</div>
<div className="flex items-start justify-between mb-4 pl-6">
<div className={`w-11 h-11 rounded-xl ${ic.bg} flex items-center justify-center`}>
<IconComp className={`w-6 h-6 ${ic.color}`} />
<div className={`w-9 h-9 rounded-lg ${ic.bg} flex items-center justify-center`}>
<IconComp className="w-5 h-5 object-contain" />
</div>
<div className="flex items-center gap-2">
<span className={`text-[11px] px-2 py-0.5 rounded-md border font-medium ${envColor[instance.env] || 'bg-[var(--dg-bg-tertiary)] text-[var(--dg-text-muted)] border-[var(--dg-border)]'}`}>
@@ -472,14 +501,14 @@ export default function InstancePage() {
</div>
</div>
<h3 className="text-base font-semibold mb-1 truncate">{instance.name}</h3>
<p className="text-xs text-[var(--dg-text-muted)] mb-1">{instance.host}:{instance.port}</p>
<p className="text-xs text-[var(--dg-text-muted)] mb-3">
<h3 className="text-sm font-semibold mb-0.5 truncate">{instance.name}</h3>
<p className="text-[11px] text-[var(--dg-text-muted)] mb-0.5">{instance.host}:{instance.port}</p>
<p className="text-[11px] text-[var(--dg-text-muted)] mb-2">
{ic.label} {instance.version || ''}
</p>
{/* Health Score */}
<div className="flex items-center gap-2 mb-3 p-2 rounded-lg bg-[var(--dg-bg-tertiary)]">
<div className="flex items-center gap-2 mb-2 p-1.5 rounded-lg bg-[var(--dg-bg-tertiary)]">
<Gauge className={`w-4 h-4 ${healthColor(health)}`} />
<div className="flex-1">
<div className="flex items-center justify-between">
@@ -495,35 +524,33 @@ export default function InstancePage() {
</div>
{/* Metrics */}
<div className="grid grid-cols-3 gap-2 text-center">
<div className="p-1.5 rounded-md bg-[var(--dg-bg-tertiary)]">
<div className="grid grid-cols-3 gap-1.5 text-center">
<div className="p-1 rounded-md bg-[var(--dg-bg-tertiary)]">
<div className="flex items-center justify-center gap-1">
<Users className="w-3 h-3 text-[var(--dg-text-muted)]" />
<span className="text-xs font-semibold">{instance.connections}</span>
<span className="text-[11px] font-semibold">{instance.connections}</span>
</div>
<span className="text-[10px] text-[var(--dg-text-muted)]">连接</span>
<span className="text-[9px] text-[var(--dg-text-muted)]">连接</span>
</div>
<div className="p-1.5 rounded-md bg-[var(--dg-bg-tertiary)]">
<div className="p-1 rounded-md bg-[var(--dg-bg-tertiary)]">
<div className="flex items-center justify-center gap-1">
<Activity className="w-3 h-3 text-[var(--dg-text-muted)]" />
<span className="text-xs font-semibold">{instance.qps ? instance.qps.toLocaleString() : '-'}</span>
<span className="text-[11px] font-semibold">{instance.qps ? instance.qps.toLocaleString() : '-'}</span>
</div>
<span className="text-[10px] text-[var(--dg-text-muted)]">QPS</span>
<span className="text-[9px] text-[var(--dg-text-muted)]">QPS</span>
</div>
<div className="p-1.5 rounded-md bg-[var(--dg-bg-tertiary)]">
<div className="p-1 rounded-md bg-[var(--dg-bg-tertiary)]">
<div className="flex items-center justify-center gap-1">
<Signal className="w-3 h-3 text-[var(--dg-text-muted)]" />
<span className="text-xs font-semibold">{instance.latency}ms</span>
<span className="text-[11px] font-semibold">{instance.latency}ms</span>
</div>
<span className="text-[10px] text-[var(--dg-text-muted)]">延迟</span>
<span className="text-[9px] text-[var(--dg-text-muted)]">延迟</span>
</div>
</div>
{instance.description && (
<p className="text-xs text-[var(--dg-text-secondary)] line-clamp-2 mt-3">{instance.description}</p>
)}
<div className="mt-3 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="mt-2 opacity-0 group-hover:opacity-100 transition-opacity">
<span className="text-[11px] text-blue-400 flex items-center gap-1">
查看详情
<ChevronRight className="w-3 h-3" />
@@ -584,7 +611,7 @@ export default function InstancePage() {
<td className="px-4 py-3 font-medium">{instance.name}</td>
<td className="px-4 py-3">
<span className={`flex items-center gap-1.5 ${ic.color}`}>
<IconComp className="w-4 h-4" />
<IconComp className="w-4 h-4 object-contain" />
<span className="text-xs">{ic.label}</span>
</span>
</td>
@@ -633,10 +660,10 @@ export default function InstancePage() {
/* ------------------------------------------------------------------ */
/* InstanceDetail */
/* ------------------------------------------------------------------ */
function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete }: {
function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete, onEdit }: {
instance: InstanceItem; onBack: () => void;
config: { icon: React.ElementType; color: string; bg: string };
canEdit: boolean; canDelete: boolean; onDelete: () => void;
canEdit: boolean; canDelete: boolean; onDelete: () => void; onEdit: () => void;
}) {
const [testing, setTesting] = useState(false);
const [testResult, setTestResult] = useState<{ success: boolean; message: string; version?: string } | null>(null);
@@ -694,7 +721,7 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
{testing ? '测试中...' : '测试连接'}
</button>
{canEdit && (
<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 text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)] transition-all">
<button onClick={onEdit} 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 text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)] transition-all">
<Edit2 className="w-4 h-4" />
编辑
</button>
@@ -845,8 +872,7 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
);
}
/* ------------------------------------------------------------------ */
/* DB Brand Icons */
/* ------------------------------------------------------------------ */
function MySQLIcon({ className }: { className?: string }) {
return <img src="/db-logos/mysql.svg" alt="MySQL" className={className} />;