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

@@ -0,0 +1,119 @@
import { memo, useCallback, useEffect, useState } from 'react'
import { Globe, Save } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import { settingsApi, type ImageRegistryConfig } from '@/api/settings'
import { toast } from 'sonner'
const MODES = [
{ value: 'overseas', label: '海外官方', desc: 'docker.io,海外网络环境' },
{ value: 'cn', label: '国内加速', desc: 'docker.m.daocloud.io 镜像,国内环境防被墙' },
{ value: 'custom', label: '私有仓库', desc: '自有 registry 前缀(如华为云 SWR)' },
] as const
/**
* 镜像源设置 — 影响平台拉取的全部镜像(Loki/Grafana/Promtail/Fluent Bit),
* 参照 kuboard 的国内/海外选择;实时预览四类镜像的生效地址。
*/
export const ImageRegistrySection = memo(function ImageRegistrySection() {
const [cfg, setCfg] = useState<ImageRegistryConfig | null>(null)
const [customHost, setCustomHost] = useState('')
const [saving, setSaving] = useState(false)
useEffect(() => {
settingsApi
.getImageRegistry()
.then((c) => {
setCfg(c)
setCustomHost(c.customHost || '')
})
.catch(() => toast.error('加载镜像源配置失败'))
}, [])
const handleSave = useCallback(async () => {
if (!cfg) return
setSaving(true)
try {
const saved = await settingsApi.saveImageRegistry({
mode: cfg.mode,
customHost: cfg.mode === 'custom' ? customHost : undefined,
})
setCfg(saved)
toast.success('镜像源已保存,后续部署/采集将使用新镜像地址')
} catch (err) {
toast.error(err instanceof Error ? err.message : '保存失败')
} finally {
setSaving(false)
}
}, [cfg, customHost])
if (!cfg) return null
return (
<Card className="border-border">
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2">
<Globe className="w-4 h-4" />
镜像仓库源
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{MODES.map((m) => (
<button
key={m.value}
type="button"
aria-pressed={cfg.mode === m.value}
onClick={() => setCfg({ ...cfg, mode: m.value })}
className={cn(
'text-left rounded-xl border-2 p-3 transition-all',
cfg.mode === m.value ? 'border-primary bg-primary/5' : 'border-border hover:border-input'
)}
>
<p className={cn('text-sm font-medium', cfg.mode === m.value && 'text-primary')}>{m.label}</p>
<p className="text-xs text-muted-foreground mt-1">{m.desc}</p>
</button>
))}
</div>
{cfg.mode === 'custom' && (
<div className="space-y-2">
<Label>私有仓库前缀 *</Label>
<Input
placeholder="swr.cn-east-2.myhuaweicloud.com/your-namespace"
value={customHost}
onChange={(e) => setCustomHost(e.target.value)}
/>
<p className="text-xs text-muted-foreground">平台会在镜像名前拼接此前缀,如 {customHost || 'your-registry'}/grafana/loki:3.4.0</p>
</div>
)}
{/* 生效镜像预览 */}
<div className="rounded-lg bg-muted/50 p-3 space-y-1.5">
<p className="text-xs font-medium text-muted-foreground">生效镜像预览</p>
{([
['Loki', cfg.sampleLoki],
['Grafana', cfg.sampleGrafana],
['Promtail', cfg.samplePromtail],
['Fluent Bit', cfg.sampleFluentBit],
] as const).map(([name, img]) => (
<p key={name} className="text-xs font-mono flex gap-2">
<span className="text-muted-foreground w-20 shrink-0">{name}</span>
<span className="truncate" title={img}>{img}</span>
</p>
))}
</div>
<div className="flex justify-end">
<Button onClick={handleSave} disabled={saving}>
<Save className="w-4 h-4 mr-1" />
{saving ? '保存中...' : '保存镜像源'}
</Button>
</div>
</CardContent>
</Card>
)
})