refactor: 弹框架构重构 + 审核规则seed API + LDAP默认配置 + 全量初始数据

弹框架构:
- Layout header/sidebar z-[200] 隔离弹框层,弹框不再遮挡面包屑
- dialog.tsx 简化: 去掉 border/backdrop-blur,统一暗色主题变量
- ModalOverlay z-[100] 对齐 Radix Dialog overlay 层级
- EditInstanceDialog 同步 z-index 修复
- 修复 InstanceGroupPage/InstancePage/LdapConfigPage 缺失 import
- 修复 InstanceGroupPage 缺失 X icon import
- RedisDetailPage ModalOverlay 迁移为 FormDialog

审核规则:
- POST /audit-rules/seed/:dbType 初始化默认规则 (mysql 11/pg 7/redis 4/mongo 4)
- POST /audit-rules/:id/toggle 开关规则
- PUT /audit-rules/:id 编辑规则
- seed.go 启动时自动创建 26 条默认审核规则

LDAP 默认配置:
- seed.go 自动写入 9 条 LDAP 配置 (8.217.205.92:389, dc=opsx,dc=vip)
- 全新 DB 启动即就位,无需手动配置
This commit is contained in:
logan committed 2026-05-29 22:48:45 +08:00
1 parent 47431f127c
commit 889cce140b
24 files changed
+494 -252

No files matched your search

@@ -137,7 +137,7 @@ export default function EditInstanceDialog({ instance, open, onClose, onSaved, m
const labelCls = 'block text-xs font-medium text-[var(--dg-text-secondary)] mb-1';
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/40">
<div
className="bg-[var(--dg-bg-secondary)] rounded-xl shadow-2xl w-full max-w-lg max-h-[85vh] overflow-y-auto"
onClick={e => e.stopPropagation()}
+4 -4
View File
@@ -52,8 +52,8 @@ export default function Layout() {
return (
<div className="flex h-screen bg-[var(--dg-bg-primary)] text-[var(--dg-text-primary)] overflow-hidden">
{/* 侧边栏 */}
<aside className="flex flex-col bg-[var(--dg-bg-secondary)] border-r border-[var(--dg-border)] w-[220px] min-w-[220px]">
{/* 侧边栏 — z-[200] 永远在弹框之上 */}
<aside className="relative z-[200] flex flex-col bg-[var(--dg-bg-secondary)] border-r border-[var(--dg-border)] w-[220px] min-w-[220px]">
{/* Logo */}
<div className="h-14 flex items-center px-4">
<div className="flex items-center gap-2.5">
@@ -123,8 +123,8 @@ export default function Layout() {
{/* 主内容区 */}
<main id="app-main" className="relative flex-1 flex flex-col min-w-0 overflow-hidden">
{/* 顶部栏 */}
<header className="h-14 bg-[var(--dg-bg-secondary)] border-b border-[var(--dg-border)] flex items-center justify-between px-5 flex-shrink-0">
{/* 顶部栏 — z-[200] 永远在弹框之上 */}
<header className="relative z-[200] h-14 bg-[var(--dg-bg-secondary)] border-b border-[var(--dg-border)] flex items-center justify-between px-5 flex-shrink-0">
{/* Breadcrumb area */}
<div className="flex items-center gap-1.5 text-sm">
<span className="text-[var(--dg-text-muted)] font-medium">OpsxSQL</span>
+7 -1
View File
@@ -8,6 +8,12 @@ interface ModalOverlayProps {
children: React.ReactNode;
}
/**
* 通用弹框组件
* - overlay z-[100],低于 header/sidebar 的 z-[200],不遮挡面包屑
* - 点击遮罩层不关闭
* - ESC 键关闭
*/
export function ModalOverlay({ open, onClose, width = 'max-w-lg', className = '', children }: ModalOverlayProps) {
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
@@ -28,7 +34,7 @@ export function ModalOverlay({ open, onClose, width = 'max-w-lg', className = ''
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/40">
<div
className={`bg-[var(--dg-bg-secondary)] rounded-xl shadow-2xl w-full ${width} max-h-[85vh] overflow-y-auto ${className}`}
onClick={(e) => e.stopPropagation()}
+3 -3
View File
@@ -1,4 +1,4 @@
import React, { useRef, useEffect, useCallback, useImperativeHandle, forwardRef } from 'react';
import { useRef, useEffect, useCallback, useImperativeHandle, forwardRef } from 'react';
import { EditorState, Compartment } from '@codemirror/state';
import type { Extension } from '@codemirror/state';
import { EditorView, keymap, lineNumbers, placeholder as cmPlaceholder } from '@codemirror/view';
@@ -125,8 +125,8 @@ function getSQLExtension(language: SqlEditorProps['language'], schema?: SqlEdito
const dialect = SQLDialect.define({
keywords: 'select from where order by group having limit offset join inner left right full outer cross on and or not in is null like between as asc desc distinct all case when then else end exists union intersect except insert into values update set delete create alter drop table index view primary key foreign references unique check default grant revoke commit rollback begin transaction count sum avg min max show describe use database explain analyze',
schema: tables,
});
...(tables ? { schema: tables } : {}),
} as any);
return sql({ dialect });
}
+1 -1
View File
@@ -68,7 +68,7 @@ const STARS = [
{ left: '60%', top: '30%', size: 1, delay: 2.2, duration: 4.2 },
];
export default function AuroraView(): JSX.Element {
export default function AuroraView() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
return (
+1 -1
View File
@@ -66,7 +66,7 @@ interface Particle {
}
/* ── 组件 ── */
export default function CosmosView(): JSX.Element {
export default function CosmosView() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
/* 生成静态星星数据 */
@@ -43,7 +43,7 @@ const KEYFRAMES = `
/* ------------------------------------------------------------------ */
/* Component */
/* ------------------------------------------------------------------ */
export default function InkWashView(): JSX.Element {
export default function InkWashView() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
return (
@@ -31,7 +31,7 @@ const KEYFRAMES = `
}
`;
export default function NeonCityView(): JSX.Element {
export default function NeonCityView() {
const { username, password, loading, error, setUsername, setPassword, handleLogin } = useLogin();
const [focus, setFocus] = useState<string | null>(null);
const isDisabled = !username || !password || loading;
+1 -1
View File
@@ -26,7 +26,7 @@ export default function SidePanel() {
onClick={toggleTheme}
className="absolute top-5 right-5 z-20 w-10 h-10 rounded-xl bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] flex items-center justify-center text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)] transition-colors"
>
{theme === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
{(theme as string) === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
</button>
{/* 左侧 */}
+26 -59
View File
@@ -1,42 +1,30 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"fixed inset-0 z-[100] bg-black/50",
"fixed inset-0 z-[100] bg-black/40",
"data-[state=open]:animate-in data-[state=closed]:animate-out",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
@@ -51,28 +39,24 @@ function DialogContent({
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
}: React.ComponentProps<typeof DialogPrimitive.Content> & { showCloseButton?: boolean }) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
onPointerDownOutside={(e) => {
e.preventDefault()
}}
onInteractOutside={(e) => {
e.preventDefault()
}}
onPointerDownOutside={(e) => e.preventDefault()}
onInteractOutside={(e) => e.preventDefault()}
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out",
"bg-[var(--dg-bg-secondary)] text-[var(--dg-text-primary)]",
"data-[state=open]:animate-in data-[state=closed]:animate-out",
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
"fixed z-[101] grid w-full max-w-[calc(100%-2rem)]",
"top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]",
"gap-4 rounded-lg border p-6 shadow-lg duration-200",
"outline-none sm:max-w-lg",
"fixed z-[101] top-[50%] left-[50%] -translate-x-1/2 -translate-y-1/2",
"w-full max-w-[calc(100%-2rem)] sm:max-w-lg",
"rounded-xl shadow-2xl",
"max-h-[85vh] flex flex-col",
"outline-none duration-200",
className
)}
{...props}
@@ -81,9 +65,9 @@ function DialogContent({
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
className="absolute top-3.5 right-3.5 rounded-md p-1 text-[var(--dg-text-muted)] opacity-70 transition-opacity hover:opacity-100 focus:outline-none"
>
<XIcon />
<XIcon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
@@ -94,48 +78,31 @@ function DialogContent({
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
<div data-slot="dialog-header" className={cn("flex items-center justify-between", className)} {...props} />
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
/>
<div data-slot="dialog-footer" className={cn("flex justify-end gap-2 pt-2", className)} {...props} />
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
className={cn("text-base font-semibold", className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
className={cn("text-sm text-[var(--dg-text-muted)]", className)}
{...props}
/>
)
@@ -148,8 +115,8 @@ export {
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
DialogOverlay,
DialogPortal,
}