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:
1 parent
327e282356
commit
f9c6c1d826
204 files changed
+14107
-9103
No files matched your search
+32
-50
@@ -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 />
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user