feat: complete Loki query, insights, settings and deployment workflows

Add the Loki query UI, access policies, ingress insights, alert and registry settings, and their backend APIs. Include the related Kubernetes deployment and collector updates required by the integrated application.

Validated with go test ./..., npm run build, and npm run lint.
This commit is contained in:
logan committed 2026-10-25 21:28:32 +08:00
1 parent f9c6c1d826
commit 2d73661dd1
127 files changed
+8117 -2876

No files matched your search

+12 -12
View File
@@ -46,10 +46,10 @@ function SidebarNav({ collapsed, onNavigate }: { collapsed: boolean; onNavigate?
onClick={onNavigate}
aria-label={item.label}
className={cn(
'flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all',
'relative flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors',
isActive
? 'bg-primary text-primary-foreground'
: 'text-slate-400 hover:bg-slate-800 hover:text-white'
? 'bg-sidebar-primary/15 text-sidebar-primary font-semibold before:absolute before:left-0 before:h-5 before:w-0.5 before:rounded-full before:bg-sidebar-primary'
: 'text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground'
)}
title={!collapsed ? item.label : undefined}
>
@@ -68,15 +68,15 @@ function SidebarNav({ collapsed, onNavigate }: { collapsed: boolean; onNavigate?
// 侧栏 Logo 区
function SidebarLogo({ collapsed }: { collapsed: boolean }) {
return (
<div className="h-16 flex items-center px-4 border-b border-slate-700 flex-shrink-0">
<div className="h-16 flex items-center px-4 border-b border-sidebar-border flex-shrink-0">
{collapsed ? (
<div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center mx-auto">
<Activity className="w-5 h-5 text-white" />
<div className="w-8 h-8 rounded-lg border border-sidebar-primary/30 bg-sidebar-primary/15 flex items-center justify-center mx-auto">
<Activity className="w-5 h-5 text-sidebar-primary" />
</div>
) : (
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center">
<Activity className="w-5 h-5 text-white" />
<div className="w-8 h-8 rounded-lg border border-sidebar-primary/30 bg-sidebar-primary/15 flex items-center justify-center">
<Activity className="w-5 h-5 text-sidebar-primary" />
</div>
<span className="font-bold text-lg tracking-tight">opsx-loki</span>
</div>
@@ -162,18 +162,18 @@ const Layout = memo(function Layout({ children }: { children: React.ReactNode })
{!isMobile && (
<aside
className={cn(
'bg-slate-900 text-white transition-all duration-300 flex flex-col flex-shrink-0',
'bg-sidebar text-sidebar-accent-foreground transition-all duration-300 flex flex-col flex-shrink-0',
collapsed ? 'w-16' : 'w-64'
)}
>
<SidebarLogo collapsed={collapsed} />
<SidebarNav collapsed={collapsed} />
<div className="p-2 border-t border-slate-700 flex-shrink-0">
<div className="p-2 border-t border-sidebar-border flex-shrink-0">
<button
onClick={toggleSidebar}
aria-label={collapsed ? '展开菜单' : '收起菜单'}
title={collapsed ? '展开菜单' : '收起菜单'}
className="flex items-center justify-center w-full py-2.5 rounded-lg text-slate-400 hover:bg-slate-800 hover:text-white transition-all"
className="flex items-center justify-center w-full py-2.5 rounded-lg text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground transition-colors"
>
{collapsed ? (
<PanelLeftOpen className="w-5 h-5" />
@@ -188,7 +188,7 @@ const Layout = memo(function Layout({ children }: { children: React.ReactNode })
{/* 移动端抽屉侧栏 */}
{isMobile && (
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
<SheetContent side="left" className="bg-slate-900 text-white border-none p-0 w-72">
<SheetContent side="left" className="bg-sidebar text-sidebar-accent-foreground border-none p-0 w-72">
<SheetHeader className="sr-only">
<SheetTitle className="text-white">opsx-loki 导航</SheetTitle>
</SheetHeader>
+8 -1
View File
@@ -1,6 +1,8 @@
import { memo } from 'react'
import { useLocation } from 'react-router'
import { cn } from '@/lib/utils'
import { PageHeader } from '@/components/PageHeader'
import { findRouteMeta } from '@/router/routes'
interface PageContainerProps {
/** 页头标题;缺省则不渲染页头(如仪表盘总览页) */
@@ -23,9 +25,14 @@ export const PageContainer = memo(function PageContainer({
className,
children,
}: PageContainerProps) {
const { pathname } = useLocation()
const routeTitle = findRouteMeta(pathname)?.title
const repeatedTitle = Boolean(title && routeTitle && title.replace(/\s/g, '') === routeTitle.replace(/\s/g, ''))
const showHeader = Boolean(title && (!repeatedTitle || description || action))
return (
<div className={cn('space-y-6', className)}>
{title && <PageHeader title={title} description={description} action={action} />}
{showHeader && <PageHeader title={repeatedTitle ? undefined : title} description={description} action={action} />}
{children}
</div>
)
+5 -7
View File
@@ -2,7 +2,7 @@ import { memo } from 'react'
import { cn } from '@/lib/utils'
interface PageHeaderProps {
title: string
title?: string
description?: string
action?: React.ReactNode
className?: string
@@ -20,13 +20,11 @@ export const PageHeader = memo(function PageHeader({
className,
}: PageHeaderProps) {
return (
<div className={cn('flex items-center justify-between', className)}>
<div>
<h2 className="text-lg font-semibold text-foreground">
{title}
</h2>
<div className={cn('flex flex-wrap items-center justify-between gap-3', className)}>
<div className="min-w-0">
{title && <h2 className="text-lg font-semibold text-foreground">{title}</h2>}
{description && (
<p className="text-sm text-muted-foreground mt-0.5">{description}</p>
<p className={cn('text-sm text-muted-foreground', title && 'mt-0.5')}>{description}</p>
)}
</div>
{action && <div className="flex-shrink-0">{action}</div>}
+97
View File
@@ -0,0 +1,97 @@
import { memo } from 'react'
import type { LucideIcon } from 'lucide-react'
import { Card, CardContent } from '@/components/ui/card'
import { cn } from '@/lib/utils'
export interface ResourceField {
label: string
value: React.ReactNode
/** 值提示(截断时 title) */
title?: string
}
interface ResourceCardProps {
/** 头部图标 */
icon: LucideIcon
/** 图标配色(text-info/success/warning 等语义 token 类) */
iconClassName?: string
title: string
/** 标题旁附加(如 ready 状态点、uuid) */
titleExtra?: React.ReactNode
/** 右上角徽章(状态/保留天数等) */
badges?: React.ReactNode
/** 副标题/描述(单行截断) */
description?: string
/** 信息字段(两列网格;奇数个最后一项占整行) */
fields: ResourceField[]
/** 底部状态消息(如错误信息,浅底块) */
message?: string
/** 底部操作区(border-top 分隔) */
actions: React.ReactNode
}
/**
* 资源卡片 — 五大资源页(集群/主机/存储/部署/采集)统一样式:
* 图标+标题+徽章 → 描述 → 两列信息网格(label 上 value 下) → 分隔线操作区。
* 统一 p-4 内边距、hover 阴影、等高布局(网格内自动对齐)。
*/
export const ResourceCard = memo(function ResourceCard({
icon: Icon,
iconClassName = 'text-muted-foreground',
title,
titleExtra,
badges,
description,
fields,
message,
actions,
}: ResourceCardProps) {
return (
<Card className="border-border hover:shadow-md transition-shadow h-full">
<CardContent className="p-4 flex flex-col gap-3 h-full">
{/* 头部:图标 + 标题 + 徽章 */}
<div className="flex items-start gap-3">
<div className="w-9 h-9 rounded-lg bg-muted/70 flex items-center justify-center shrink-0">
<Icon className={cn('size-4', iconClassName)} />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 flex-wrap">
<h3 className="font-medium text-sm text-foreground truncate" title={title}>
{title}
</h3>
{titleExtra}
{badges && <span className="ml-auto flex items-center gap-1.5 shrink-0">{badges}</span>}
</div>
{description && (
<p className="text-xs text-muted-foreground truncate mt-0.5" title={description}>
{description}
</p>
)}
</div>
</div>
{/* 信息:两列网格(label 灰小字在上,value 在下) */}
<div className="grid grid-cols-2 gap-x-4 gap-y-2 text-xs">
{fields.map((f) => (
<div key={f.label} className="min-w-0">
<p className="text-muted-foreground leading-4">{f.label}</p>
<p className="font-medium text-foreground truncate leading-5" title={f.title}>
{f.value}
</p>
</div>
))}
</div>
{/* 状态消息(错误/提示) */}
{message && (
<p className="rounded-md bg-muted/60 px-2.5 py-1.5 text-xs text-muted-foreground" title={message}>
{message}
</p>
)}
{/* 操作区:贴底 */}
<div className="flex gap-2 pt-3 border-t border-border mt-auto">{actions}</div>
</CardContent>
</Card>
)
})
+1
View File
@@ -3,6 +3,7 @@
export { default as PageContainer } from './PageContainer';
export { default as PageHeader } from './PageHeader';
export { default as InfoRow } from './InfoRow';
export { ResourceCard } from './ResourceCard';
export { default as ConfirmDialog } from './ConfirmDialog';
export { default as ErrorState } from './ErrorState';
export { default as EmptyState } from './EmptyState';