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:
loganandClaude Opus 4.7 committed 2026-05-22 11:11:57 +08:00
commit 439155c92e
290 files changed
+61950

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}
+164
View File
@@ -0,0 +1,164 @@
import { createContext, useContext, useState, useCallback, useMemo } from 'react';
import { menuItems, currentUser as defaultUser } from '@/lib/mock';
import { authApi } from '@/api/auth';
export type UserRole = 'admin' | 'dba' | 'developer' | 'guest';
export interface AuthUser {
id: number;
username: string;
email: string;
role: UserRole;
roleName: string;
avatar: string;
}
interface AuthContextType {
user: AuthUser;
isLoggedIn: boolean;
login: (username: string, password: string) => Promise<boolean>;
logout: () => void;
switchRole: (role: UserRole) => void;
hasPermission: (menuId: string) => boolean;
visibleMenuItems: typeof menuItems;
isAdmin: boolean;
isDBA: boolean;
isDeveloper: boolean;
isGuest: boolean;
}
const roleNameMap: Record<UserRole, string> = {
admin: '超级管理员',
dba: '数据库管理员',
developer: '开发人员',
guest: '访客',
};
const routePermissionMap: Record<string, UserRole[]> = {
dashboard: ['admin', 'dba', 'developer', 'guest'],
mysql: ['admin', 'dba'],
postgres: ['admin', 'dba'],
redis: ['admin', 'dba'],
mongo: ['admin', 'dba'],
tickets: ['admin', 'dba', 'developer'],
queries: ['admin', 'dba', 'developer', 'guest'],
roles: ['admin'],
users: ['admin'],
'data-permission': ['admin', 'dba'],
'audit-rules': ['admin', 'dba'],
workflow: ['admin', 'dba'],
'auto-task': ['admin'],
announcement: ['admin'],
'config-platform': ['admin'],
notifications: ['admin'],
};
export const actionPermissionMap: Record<string, UserRole[]> = {
'ticket.create': ['admin', 'dba', 'developer'],
'ticket.approve': ['admin', 'dba'],
'ticket.execute': ['admin', 'dba'],
'ticket.rollback': ['admin', 'dba'],
'ticket.delete': ['admin'],
'query.create': ['admin', 'dba', 'developer'],
'query.export': ['admin', 'dba', 'developer'],
'instance.add': ['admin'],
'instance.edit': ['admin', 'dba'],
'instance.delete': ['admin'],
'user.manage': ['admin'],
'role.manage': ['admin'],
'rule.manage': ['admin', 'dba'],
'workflow.manage': ['admin', 'dba'],
'config.manage': ['admin'],
'announcement.manage': ['admin'],
'auto-task.manage': ['admin'],
};
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<AuthUser>({
id: defaultUser.id,
username: defaultUser.username,
email: defaultUser.email,
role: defaultUser.role,
roleName: defaultUser.roleName,
avatar: defaultUser.avatar,
});
const [isLoggedIn, setIsLoggedIn] = useState(() => !!localStorage.getItem('token'));
const login = useCallback(async (username: string, password: string): Promise<boolean> => {
try {
const res = await authApi.login(username, password);
localStorage.setItem('token', res.access_token);
const u = res.user;
const role = u.role as UserRole;
setUser({
id: u.id,
username: u.username,
email: u.email || '',
role,
roleName: roleNameMap[role] || role,
avatar: u.display_name?.charAt(0) || username.charAt(0).toUpperCase(),
});
setIsLoggedIn(true);
return true;
} catch {
return false;
}
}, []);
const logout = useCallback(() => {
localStorage.removeItem('token');
setIsLoggedIn(false);
}, []);
const switchRole = useCallback((role: UserRole) => {
setUser(prev => ({
...prev,
role,
roleName: roleNameMap[role],
username: role === 'admin' ? 'admin' : role === 'dba' ? 'zhangsan' : role === 'developer' ? 'wangwu' : 'visitor',
email: `${role}@company.com`,
avatar: role.charAt(0).toUpperCase(),
}));
}, []);
const hasPermission = useCallback((menuId: string): boolean => {
const allowedRoles = routePermissionMap[menuId];
if (!allowedRoles) return false;
return allowedRoles.includes(user.role);
}, [user.role]);
const visibleMenuItems = useMemo(() => {
return menuItems.filter(item => item.roles.includes(user.role));
}, [user.role]);
const value = useMemo(() => ({
user,
isLoggedIn,
login,
logout,
switchRole,
hasPermission,
visibleMenuItems,
isAdmin: user.role === 'admin',
isDBA: user.role === 'dba',
isDeveloper: user.role === 'developer',
isGuest: user.role === 'guest',
}), [user, isLoggedIn, login, logout, switchRole, hasPermission, visibleMenuItems]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextType {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}
export function useActionPermission(action: string): boolean {
const { user } = useAuth();
const allowedRoles = actionPermissionMap[action];
if (!allowedRoles) return false;
return allowedRoles.includes(user.role);
}
+61
View File
@@ -0,0 +1,61 @@
import { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react';
export type Theme = 'dark' | 'light';
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
setTheme: (theme: Theme) => void;
isDark: boolean;
isLight: boolean;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
function getInitialTheme(): Theme {
if (typeof window === 'undefined') return 'dark';
const stored = localStorage.getItem('dg-theme') as Theme | null;
if (stored === 'dark' || stored === 'light') return stored;
return 'dark';
}
function applyTheme(theme: Theme) {
document.documentElement.setAttribute('data-theme', theme);
}
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>(getInitialTheme);
useEffect(() => {
applyTheme(theme);
}, [theme]);
const toggleTheme = useCallback(() => {
setThemeState(prev => {
const next = prev === 'dark' ? 'light' : 'dark';
localStorage.setItem('dg-theme', next);
return next;
});
}, []);
const setTheme = useCallback((t: Theme) => {
localStorage.setItem('dg-theme', t);
setThemeState(t);
}, []);
const value = useMemo(() => ({
theme,
toggleTheme,
setTheme,
isDark: theme === 'dark',
isLight: theme === 'light',
}), [theme, toggleTheme, setTheme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
export function useTheme(): ThemeContextType {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}