feat: 修复仪表盘数据管线 — 补充应用发布种子数据 + 修复limit参数解析 + LDAP认证集成 + User类型完善

后端改进:
- 修复 dashboard/recent-alerts 的 limit 参数解析 (原代码错误地将数字解析为日期)
- 为 dashboard/recent-releases 添加 limit 参数解析支持
- 新增 generateAppReleases 种子数据生成函数 (10条应用发布记录)
- 修复 AlertEvent 的 rule_name/rule_id/severity 数据一致性
- 添加 go-ldap/ldap/v3 依赖并集成 LDAP 认证处理器
- 添加 seed.go 自动种子数据初始化 (admin用户+LDAP配置)
- User model 添加 AuthSource/LDAPConfig 字段

前端改进:
- 修复 User 类型定义以匹配后端 API (real_name/is_admin/auth_source等)
- 修复 Header.tsx 的 real_name 属性引用导致 TypeScript 编译错误
- 统一 API 服务层、暗色主题样式、组件联调优化

验证:
- go build ./... 编译通过
- npm run build 构建通过
- 仪表盘发布历史、告警消息等区域数据正常展示
- 健康检查 /health 正常
This commit is contained in:
attacker committed 2026-06-14 02:11:10 +08:00
1 parent 97c4207ff8
commit ff0b268984
39 files changed
+4283 -1878

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
import { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react';
export type Theme =
| 'ocean-deep'
| 'light-professional'
| 'midnight-purple'
| 'pine-green'
| 'orange-vitality';
export interface ThemeOption {
id: Theme;
name: string;
description: string;
isDark: boolean;
primaryColor: string;
}
export const THEME_OPTIONS: ThemeOption[] = [
{ id: 'ocean-deep', name: '深海蓝', description: '专业稳重的深蓝色调', isDark: true, primaryColor: '#3b82f6' },
{ id: 'light-professional', name: '明亮白', description: '清爽干净的浅色主题', isDark: false, primaryColor: '#2563eb' },
{ id: 'midnight-purple', name: '午夜紫', description: '神秘优雅的紫色调', isDark: true, primaryColor: '#a855f7' },
{ id: 'pine-green', name: '松林绿', description: '自然清新的绿色主题', isDark: true, primaryColor: '#22c55e' },
{ id: 'orange-vitality', name: '活力橙', description: '温暖活力的橙色主题', isDark: true, primaryColor: '#f97316' },
];
const THEME_ORDER: Theme[] = THEME_OPTIONS.map((o) => o.id);
const DEFAULT_THEME: Theme = 'ocean-deep';
const STORAGE_KEY = 'aiops-theme';
// Backward compatibility: map old theme names to new ones
function migrateLegacyTheme(value: string | null): Theme | null {
if (value === 'dark') return 'ocean-deep';
if (value === 'light') return 'light-professional';
return null;
}
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
function getInitialTheme(): Theme {
if (typeof window === 'undefined') return DEFAULT_THEME;
const stored = localStorage.getItem(STORAGE_KEY);
const migrated = migrateLegacyTheme(stored);
if (migrated) {
// Persist the migrated value so we don't migrate every time
localStorage.setItem(STORAGE_KEY, migrated);
return migrated;
}
if (stored && THEME_ORDER.includes(stored as Theme)) {
return stored as Theme;
}
return DEFAULT_THEME;
}
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 idx = THEME_ORDER.indexOf(prev);
const next = THEME_ORDER[(idx + 1) % THEME_ORDER.length];
localStorage.setItem(STORAGE_KEY, next);
return next;
});
}, []);
const setTheme = useCallback((t: Theme) => {
localStorage.setItem(STORAGE_KEY, t);
setThemeState(t);
}, []);
const value = useMemo(
() => ({
theme,
toggleTheme,
setTheme,
}),
[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;
}