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 const COMPONENTS = [ { key: 'loki', label: 'Loki', repo: 'grafana/loki', tagHint: '跟随部署版本' }, { key: 'grafana', label: 'Grafana', repo: 'grafana/grafana', tagHint: '跟随部署版本' }, { key: 'promtail', label: 'Promtail', repo: 'grafana/promtail', tagHint: '3.4.0' }, { key: 'fluentbit', label: 'Fluent Bit', repo: 'fluent/fluent-bit', tagHint: '5.1.5' }, ] as const /** * 镜像源设置 — 影响平台拉取的全部镜像(Loki/Grafana/Promtail/Fluent Bit), * 参照 kuboard 的国内/海外选择;实时预览四类镜像的生效地址。 */ export const ImageRegistrySection = memo(function ImageRegistrySection() { const [cfg, setCfg] = useState(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, images: cfg.images, }) setCfg(saved) toast.success('镜像源已保存,后续部署/采集将使用新镜像地址') } catch (err) { toast.error(err instanceof Error ? err.message : '保存失败') } finally { setSaving(false) } }, [cfg, customHost]) if (!cfg) return null return ( 镜像仓库源
{MODES.map((m) => ( ))}
{cfg.mode === 'custom' && (
setCustomHost(e.target.value)} />

平台会在镜像名前拼接此前缀,如 {customHost || 'your-registry'}/grafana/loki:3.4.0

)} {/* 各服务镜像自定义 */}
{COMPONENTS.map(({ key, label, repo, tagHint }) => { const img = cfg.images?.[key] || { repository: '', tag: '' } const update = (patch: Partial<{ repository: string; tag: string }>) => setCfg({ ...cfg, images: { ...(cfg.images || {}), [key]: { ...img, ...patch } }, }) return (
{label} update({ repository: e.target.value })} className="font-mono text-xs" /> update({ tag: e.target.value })} className="font-mono text-xs" />
) })}

仓库填镜像路径(如 grafana/loki);Tag 留空时 Loki / Grafana 跟随部署版本,Promtail / Fluent Bit 用内置默认。

{/* 生效镜像预览 */}

生效镜像预览

{([ ['Loki', cfg.sampleLoki], ['Grafana', cfg.sampleGrafana], ['Promtail', cfg.samplePromtail], ['Fluent Bit', cfg.sampleFluentBit], ] as const).map(([name, img]) => (

{name} {img}

))}
) })