feat: opsx-loki 全面升级 — 前端重构/用户与LDAP/部署向导/日志查询

基于 WEB-UI-Development-Guide 标准的整体升级,并更名 LokiDeploy → opsx-loki。

前端(Gin+React+shadcn/ui):
- 按 02/07 章重构:页面域化拆分(无超 400 行文件)、PageContainer/ListQuery、
  反馈分级(成功静默/失败 toast/危险确认)、401 单飞刷新+队列重放
- 暗色模式(next-themes)+ 语义 Token(--primary 品牌色/success/warning/info)
- 部署向导(状态推导四步:环境→存储→Loki→采集,内嵌表单闭环)
- 日志查询页:LogQL 标签构建器/拖拽直方图/日志上下文/Live Tail/查询历史
- 按用户+标签策略的查询授权(服务端强制合并策略)

后端(Go+Gin+JSON DB):
- 简单用户管理(仅 admin,无 RBAC)+ TokenVersion 会话吊销 + 登录 IP 限流
- LDAP 接入:配置(AES 加密存储/hasPassword 脱敏)/测试连接/批量导入/登录
- 存储配置测试连接(SigV4 零依赖)+ 桶不存在自动创建
- Loki 三模式部署修复:SSH 密钥认证、3.x 配置模板、ruler 对象存储嵌套块、
  凭证解密、孤儿 deploying 状态恢复、Grafana 部署(可选默认关)+面板导入
- DaemonSet 采集纯 YAML(去 helm 依赖)+ DNS 自愈回退 ClusterIP + 集群名标签
- 日志查询代理:cluster-svc 经 kube-apiserver proxy/url 直连 + cursor 翻页
- 统一 log 命名空间;部署就绪探活;Grafana 访问地址自动记录

修复:run.sh 增量编译检测、Sanitized 脱敏补全、LokiDeployment Update 白名单、
健康检查 CreatedAt 零值等 10+ 存量 bug。

数据兼容:DB 沿用旧 lokideploy.json(新名优先);localStorage 一次性迁移;
HKDF 加密前缀保留(已加密凭证不受影响)。
This commit is contained in:
logan committed 2026-10-24 13:20:12 +08:00
1 parent 327e282356
commit f9c6c1d826
204 files changed
+14107 -9103

No files matched your search

+32 -50
View File
@@ -1,75 +1,58 @@
import { lazy, Suspense, memo } from 'react'
import { Routes, Route, Navigate } from 'react-router'
import { lazy, Suspense, memo, useEffect } from 'react'
import { Routes, Route, Navigate, useLocation } from 'react-router'
import { Toaster } from '@/components/ui/sonner'
import { LoadingSpinner } from '@/components/LoadingSpinner'
import { isLoggedIn, getUser } from '@/lib/auth-storage'
import { routes, findRouteMeta } from '@/router/routes'
// 路由守卫组件 - 独立定义,便于复用
const Layout = lazy(() => import('@/components/Layout'))
// 路由守卫 - 未登录跳登录页并携带来源路径(登录成功后回跳,见 lib/redirect.ts)
const RequireAuth = memo(function RequireAuth({ children }: { children: React.ReactNode }) {
const token = localStorage.getItem('token')
if (!token) {
return <Navigate to="/login" replace />
const location = useLocation()
if (!isLoggedIn()) {
const from = location.pathname + location.search
return <Navigate to={`/login?redirect=${encodeURIComponent(from)}`} replace />
}
if (getUser()?.role !== 'admin' && location.pathname !== '/logs') return <Navigate to="/logs" replace />
// 动态导入Layout以避免在登录页面加载它
return <LazyLayout>{children}</LazyLayout>
})
// 懒加载页面组件 - 实现代码分割
const Login = lazy(() => import('@/pages/Login'))
const Dashboard = lazy(() => import('@/pages/Dashboard'))
const Clusters = lazy(() => import('@/pages/Clusters'))
const Hosts = lazy(() => import('@/pages/Hosts'))
const Storage = lazy(() => import('@/pages/Storage'))
const Deployments = lazy(() => import('@/pages/Deployments'))
const Collectors = lazy(() => import('@/pages/Collectors'))
const Settings = lazy(() => import('@/pages/Settings'))
const Layout = lazy(() => import('@/components/Layout'))
// 懒加载布局包装器
// 懒加载布局包装器(Layout chunk 加载期间的整屏占位)
function LazyLayout({ children }: { children: React.ReactNode }) {
return (
<Suspense fallback={<PageLoadingFallback />}>
<Suspense fallback={<AppLoadingFallback />}>
<Layout>{children}</Layout>
</Suspense>
)
}
// 页面加载中占位
function PageLoadingFallback() {
// 整屏加载占位
function AppLoadingFallback() {
return (
<div className="h-screen w-screen flex items-center justify-center bg-slate-50 dark:bg-slate-950">
<LoadingSpinner size="lg" text="页面加载中..." />
<div className="h-screen w-screen flex items-center justify-center bg-muted dark:bg-slate-950">
<div className="w-6 h-6 border-2 border-slate-300 border-t-primary rounded-full animate-spin" />
</div>
)
}
// 路由配置 - 集中管理路由定义
interface RouteConfig {
path: string
element: React.ReactNode
auth?: boolean
// document.title 由路由 meta 单源驱动
function useDocumentTitle() {
const location = useLocation()
useEffect(() => {
const title = findRouteMeta(location.pathname)?.title
document.title = title && location.pathname !== '/login' ? `${title} · opsx-loki` : 'opsx-loki'
}, [location.pathname])
}
const routes: RouteConfig[] = [
{ path: '/login', element: <Login />, auth: false },
{ path: '/', element: <Dashboard />, auth: true },
{ path: '/clusters', element: <Clusters />, auth: true },
{ path: '/hosts', element: <Hosts />, auth: true },
{ path: '/storage', element: <Storage />, auth: true },
{ path: '/deployments', element: <Deployments />, auth: true },
{ path: '/collectors', element: <Collectors />, auth: true },
{ path: '/settings', element: <Settings />, auth: true },
]
/**
* App 根组件
*
* 优化点:
* 1. 所有页面使用 React.lazy 懒加载,实现代码分割
* 2. 路由守卫独立为 memo 组件
* 3. Suspense 统一处理加载状态
* 4. 路由配置集中管理
* - 路由配置集中在 router/routes.tsx(meta 驱动标题/菜单)
* - 页面全部 React.lazy 懒加载实现代码分割
* - 页面级 Suspense 在 Layout 的内容区,切换页面时布局不闪
*/
const App = memo(function App() {
useDocumentTitle()
return (
<>
<Routes>
@@ -79,16 +62,15 @@ const App = memo(function App() {
path={route.path}
element={
route.auth ? (
<RequireAuth>{route.element}</RequireAuth>
<RequireAuth>{<route.component />}</RequireAuth>
) : (
<Suspense fallback={<PageLoadingFallback />}>
{route.element}
<Suspense fallback={<AppLoadingFallback />}>
<route.component />
</Suspense>
)
}
/>
))}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
<Toaster />
</>