feat: 平台配置全面生效 — 配置策略接入实际逻辑

- query组: ExecuteQuery读取query_limit_max/query_timeout/query_dangerous_pattern
  - 查询超时通过context.WithTimeout生效,db.QueryContext/QueryRowContext
  - 危险模式检测拦截无WHERE的DELETE/UPDATE/DROP/TRUNCATE
  - LIMIT上限从settings读取,不再硬编码1000

- security组: Login读取login_max_attempts/login_lockout_minutes
  - 新增User字段: failed_login_attempts + locked_until
  - 登录失败达到阈值自动锁定,成功时清除计数
  - JWT Token过期时间读取session_timeout_hours设置

- executor组: ExecuteTicket读取executor_timeout/executor_dry_run
  - 执行超时通过context.WithTimeout控制
  - 开启dry_run时先EXPLAIN预检查SQL语法
  - db.ExecContext替代db.Exec

- cleanup组: SettingsCleanupPreview/Run从占位改为真实实现
  - 预览统计4类数据(工单/查询历史/慢日志/审计日志)待清理数量
  - 执行清理按retention_days配置删除过期数据

- ghost组: 13项gh-ost配置加入seedDefaultSettings
  - enabled/path/mode/osc_priority/env/cloud/trigger/chunk/load/lag等
  - 已被ghost.go中getGhostConfig等函数读取

- 前端: PlatformConfigPage新增Ghost/OSC分组显示
This commit is contained in:
logan committed 2026-06-01 01:00:23 +08:00
1 parent 8af868b3ca
commit bc2092a980
11 files changed
+365 -45

No files matched your search

+36 -20
View File
@@ -71,6 +71,7 @@ const envColor: Record<string, string> = {
const statusLabel: Record<string, { text: string; online: boolean }> = {
connected: { text: '在线', online: true },
online: { text: '在线', online: true },
active: { text: '在线', online: true },
disconnected: { text: '离线', online: false },
offline: { text: '离线', online: false },
error: { text: '异常', online: false },
@@ -146,6 +147,7 @@ export default function InstancePage() {
const [batchDeleting, setBatchDeleting] = useState(false);
const [showClone, setShowClone] = useState(false);
const [cloneSource, setCloneSource] = useState<InstanceItem | null>(null);
const [deleteConfirm, setDeleteConfirm] = useState<{msg: string; onConfirm: () => void} | null>(null);
const typeTabs = [
{ key: 'all', label: '全部', icon: LayoutGrid },
@@ -729,11 +731,11 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
}) {
const [testing, setTesting] = useState(false);
const [testResult, setTestResult] = useState<{ success: boolean; message: string; version?: string } | null>(null);
const [metrics, setMetrics] = useState<{ connections: number; qps: number; latency: number; uptime: string } | null>(null);
const [metrics, setMetrics] = useState<{ connections: number; qps: number; latency: number; uptime: string; cpu?: number; memory?: number; disk?: number; used_memory_human?: string; threads_running?: number; max_connections?: number; slow_queries?: number; data_size_mb?: number } | null>(null);
useEffect(() => {
instanceApi.metrics(instance.id).then(res => {
setMetrics({ connections: res.connections ?? 0, qps: res.qps ?? 0, latency: res.latency ?? 0, uptime: res.uptime ?? '-' });
setMetrics({ connections: res.connections ?? 0, qps: res.qps ?? 0, latency: res.latency ?? 0, uptime: res.uptime ?? '-', cpu: res.cpu, memory: res.memory, disk: res.disk, used_memory_human: res.used_memory_human, threads_running: res.threads_running, max_connections: res.max_connections, slow_queries: res.slow_queries, data_size_mb: res.data_size_mb });
}).catch(() => {});
}, [instance.id]);
@@ -860,21 +862,35 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
</div>
</div>
<div className="space-y-2">
{[
{ label: 'CPU 使用率', value: instance.cpu ?? 0, color: (instance.cpu ?? 0) > 80 ? 'bg-red-400' : (instance.cpu ?? 0) > 60 ? 'bg-yellow-400' : 'bg-green-400' },
{ label: '内存使用率', value: instance.memory ?? 0, color: (instance.memory ?? 0) > 85 ? 'bg-red-400' : (instance.memory ?? 0) > 70 ? 'bg-yellow-400' : 'bg-green-400' },
{ label: '磁盘使用率', value: instance.disk ?? 0, color: (instance.disk ?? 0) > 80 ? 'bg-red-400' : (instance.disk ?? 0) > 60 ? 'bg-yellow-400' : 'bg-green-400' },
].map(item => (
<div key={item.label}>
<div className="flex justify-between text-xs mb-1">
<span className="text-[var(--dg-text-muted)]">{item.label}</span>
<span className="font-medium">{item.value}%</span>
</div>
<div className="h-1.5 rounded-full bg-[var(--dg-bg-tertiary)] overflow-hidden">
<div className={`h-full rounded-full transition-all duration-500 ${item.color}`} style={{ width: `${item.value}%` }} />
</div>
</div>
))}
{(() => {
const isRedis = instance.db_type === 'redis';
// Dynamic metric items based on db_type
const items = isRedis ? [
{ label: 'CPU 使用率', value: metrics?.cpu, text: undefined, color: (metrics?.cpu ?? 0) > 80 ? 'bg-red-400' : (metrics?.cpu ?? 0) > 60 ? 'bg-yellow-400' : 'bg-green-400' },
{ label: '内存使用率', value: metrics?.memory, text: metrics?.used_memory_human, color: (metrics?.memory ?? 0) > 85 ? 'bg-red-400' : (metrics?.memory ?? 0) > 70 ? 'bg-yellow-400' : 'bg-green-400' },
] : [
{ label: '连接使用率', value: metrics?.cpu, text: undefined, color: (metrics?.cpu ?? 0) > 80 ? 'bg-red-400' : (metrics?.cpu ?? 0) > 60 ? 'bg-yellow-400' : 'bg-green-400' },
{ label: 'Buffer Pool', value: metrics?.memory, text: undefined, color: (metrics?.memory ?? 0) > 85 ? 'bg-red-400' : (metrics?.memory ?? 0) > 70 ? 'bg-yellow-400' : 'bg-green-400' },
{ label: '慢查询', value: undefined, text: metrics?.slow_queries != null ? String(metrics.slow_queries) + ' 条' : undefined, color: '' },
];
return items.filter(item => item.value != null || item.text != null).map(item => {
const hasPercent = item.value != null && item.value >= 0;
const pct = item.value ?? 0;
return (
<div key={item.label}>
<div className="flex justify-between text-xs mb-1">
<span className="text-[var(--dg-text-muted)]">{item.label}</span>
<span className="font-medium">{hasPercent ? `${pct}%` : (item.text || 'N/A')}</span>
</div>
{hasPercent && (
<div className="h-1.5 rounded-full bg-[var(--dg-bg-tertiary)] overflow-hidden">
<div className={`h-full rounded-full transition-all duration-500 ${item.color}`} style={{ width: `${Math.max(pct, 1)}%` }} />
</div>
)}
</div>
);
});
})()}
</div>
</CardContent>
</Card>
@@ -890,9 +906,9 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
<CardContent className="p-5">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
{[
{ label: '连接数', value: instance.connections ?? metrics?.connections ?? 0, unit: '', icon: Users, color: 'text-blue-400', bg: 'bg-blue-400/10' },
{ label: 'QPS', value: instance.qps ?? metrics?.qps ?? 0, unit: '/s', icon: Activity, color: 'text-green-400', bg: 'bg-green-400/10' },
{ label: '延迟', value: instance.latency ?? metrics?.latency ?? 0, unit: 'ms', icon: Signal, color: 'text-orange-400', bg: 'bg-orange-400/10' },
{ label: '连接数', value: metrics?.connections ?? 0, unit: '', icon: Users, color: 'text-blue-400', bg: 'bg-blue-400/10' },
{ label: 'QPS', value: metrics?.qps ?? 0, unit: '/s', icon: Activity, color: 'text-green-400', bg: 'bg-green-400/10' },
{ label: '延迟', value: metrics?.latency ?? 0, unit: 'ms', icon: Signal, color: 'text-orange-400', bg: 'bg-orange-400/10' },
{ label: '运行时间', value: metrics?.uptime ?? '-', unit: '', icon: HardDrive, color: 'text-purple-400', bg: 'bg-purple-400/10' },
].map(m => (
<div key={m.label} className="p-3 rounded-lg bg-[var(--dg-bg-tertiary)] text-center">
+5 -4
View File
@@ -80,11 +80,12 @@ function CleanupActions() {
/* ── 分组元信息 ── */
const GROUP_META: Record<string, { label: string; icon: string; desc: string }> = {
query: { label: '查询控制', icon: '🔍', desc: 'LIMIT 上限、超时、自动 LIMIT' },
security: { label: '安全策略', icon: '🔒', desc: '登录安全、密码策略' },
query: { label: '查询控制', icon: '🔍', desc: 'LIMIT 上限、超时、自动 LIMIT、危险检测' },
security: { label: '安全策略', icon: '🔒', desc: '登录锁定、密码策略、会话超时' },
data_masking: { label: '数据脱敏', icon: '🛡️', desc: '控制查询结果中的敏感字段脱敏' },
cleanup: { label: '日志清理', icon: '🧹', desc: '工单、审计日志、查询历史保留天数' },
executor: { label: '执行器', icon: '⚙️', desc: 'SQL 执行超时' },
cleanup: { label: '日志清理', icon: '🧹', desc: '工单、审计日志、查询历史、慢日志保留天数' },
executor: { label: '执行器', icon: '⚙️', desc: 'SQL 执行超时、预检查、备份' },
ghost: { label: 'Ghost/OSC', icon: '👻', desc: 'gh-ost 在线 DDL 变更工具配置' },
};
export default function PlatformConfigPage() {