feat: 品牌统一 LokiStack + 全站中英 i18n + 用户批量删除 + 主题定时切换与浅色侧栏
- 品牌: Go 模块/数据文件/二进制/构建部署资产全部 opsx→lokistack;前端 localStorage 命名空间迁移(读旧写新,含按用户 id 前缀迁移);修复 nginx upstream 与 Makefile 引用已改名服务的断裂 - i18n: react-i18next 落地,common + 11 个页面域命名空间全站中英(菜单/顶栏/登录/列表页/表单/确认弹窗/状态徽章);设置页语言即时切换;修复词典自注册导致的循环导入白屏 - 用户管理: 批量删除(POST /users/batch-delete,不删自己/最后管理员,部分失败不阻断)+ 行复选/全选/批量工具条 - 主题: 浅色侧栏(中性灰激活态)、四态模式(浅色/深色/跟随系统/定时切换,按时区判定)、顶栏太阳月亮二态、设置页紧凑分段选择器 - UI: 登录记住用户名、CardActionButton 统一五页卡片操作钮、shadcn Badge 替换手写徽章、语义色修正 - 清理: 移除主机 Web 终端(前后端)、k8s-helm/Helm 遗留代码与字段、README 重写精简 - 修复: 侧栏与日志告警 order 重复、路由标题 i18n key 未翻译、词典双逗号等
This commit is contained in:
1 parent
02d1bd87a7
commit
f3733f0bfc
201 files changed
+5438
-2388
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Globe, Save } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -8,28 +9,31 @@ 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 { t } = useTranslation('settings')
|
||||
const [cfg, setCfg] = useState<ImageRegistryConfig | null>(null)
|
||||
const [customHost, setCustomHost] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
// 源选项(文案走词典;value 保持字面量类型以匹配 cfg.mode)
|
||||
const MODES = [
|
||||
{ value: 'overseas', label: t('registry.modeOverseasLabel'), desc: t('registry.modeOverseasDesc') },
|
||||
{ value: 'cn', label: t('registry.modeCnLabel'), desc: t('registry.modeCnDesc') },
|
||||
{ value: 'custom', label: t('registry.modeCustomLabel'), desc: t('registry.modeCustomDesc') },
|
||||
] as const
|
||||
|
||||
// 组件镜像行(repo 为 placeholder 示例;tagHint 区分「跟随部署版本」与内置版本号)
|
||||
const COMPONENTS = [
|
||||
{ key: 'loki', label: 'Loki', repo: 'grafana/loki', tagHint: t('registry.tagFollowVersion') },
|
||||
{ key: 'grafana', label: 'Grafana', repo: 'grafana/grafana', tagHint: t('registry.tagFollowVersion') },
|
||||
{ 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
|
||||
|
||||
useEffect(() => {
|
||||
settingsApi
|
||||
.getImageRegistry()
|
||||
@@ -37,7 +41,9 @@ export const ImageRegistrySection = memo(function ImageRegistrySection() {
|
||||
setCfg(c)
|
||||
setCustomHost(c.customHost || '')
|
||||
})
|
||||
.catch(() => toast.error('加载镜像源配置失败'))
|
||||
.catch(() => toast.error(t('registry.errLoad')))
|
||||
// 仅首载拉取配置;t 只影响失败 toast,不随语言切换重跑(避免覆盖未保存输入)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
@@ -50,13 +56,13 @@ export const ImageRegistrySection = memo(function ImageRegistrySection() {
|
||||
images: cfg.images,
|
||||
})
|
||||
setCfg(saved)
|
||||
toast.success('镜像源已保存,后续部署/采集将使用新镜像地址')
|
||||
toast.success(t('registry.savedToast'))
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : '保存失败')
|
||||
toast.error(err instanceof Error ? err.message : t('notify.errSave'))
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}, [cfg, customHost])
|
||||
}, [cfg, customHost, t])
|
||||
|
||||
if (!cfg) return null
|
||||
|
||||
@@ -65,7 +71,7 @@ export const ImageRegistrySection = memo(function ImageRegistrySection() {
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Globe className="w-4 h-4" />
|
||||
镜像仓库源
|
||||
{t('registry.cardTitle')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
@@ -89,19 +95,21 @@ export const ImageRegistrySection = memo(function ImageRegistrySection() {
|
||||
|
||||
{cfg.mode === 'custom' && (
|
||||
<div className="space-y-2">
|
||||
<Label>私有仓库前缀 *</Label>
|
||||
<Label>{t('registry.customPrefixLabel')}</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>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('registry.customPrefixHint', { example: customHost || 'your-registry' })}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 各服务镜像自定义 */}
|
||||
<div className="space-y-2">
|
||||
<Label>各服务镜像(可单独覆盖仓库 / Tag)</Label>
|
||||
<Label>{t('registry.imagesLabel')}</Label>
|
||||
<div className="space-y-2">
|
||||
{COMPONENTS.map(({ key, label, repo, tagHint }) => {
|
||||
const img = cfg.images?.[key] || { repository: '', tag: '' }
|
||||
@@ -114,14 +122,14 @@ export const ImageRegistrySection = memo(function ImageRegistrySection() {
|
||||
<div key={key} className="grid items-center gap-2 sm:grid-cols-[110px_1fr_150px]">
|
||||
<span className="text-sm font-medium">{label}</span>
|
||||
<Input
|
||||
aria-label={`${label} 镜像仓库`}
|
||||
aria-label={t('registry.repoAria', { label })}
|
||||
value={img.repository}
|
||||
placeholder={repo}
|
||||
onChange={(e) => update({ repository: e.target.value })}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<Input
|
||||
aria-label={`${label} 镜像 Tag`}
|
||||
aria-label={t('registry.tagAria', { label })}
|
||||
value={img.tag}
|
||||
placeholder={tagHint}
|
||||
onChange={(e) => update({ tag: e.target.value })}
|
||||
@@ -132,13 +140,15 @@ export const ImageRegistrySection = memo(function ImageRegistrySection() {
|
||||
})}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
仓库填镜像路径(如 <code>grafana/loki</code>);Tag 留空时 Loki / Grafana 跟随部署版本,Promtail / Fluent Bit 用内置默认。
|
||||
{t('registry.imagesHintBefore')}
|
||||
<code>grafana/loki</code>
|
||||
{t('registry.imagesHintAfter')}
|
||||
</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>
|
||||
<p className="text-xs font-medium text-muted-foreground">{t('registry.previewTitle')}</p>
|
||||
{([
|
||||
['Loki', cfg.sampleLoki],
|
||||
['Grafana', cfg.sampleGrafana],
|
||||
@@ -155,7 +165,7 @@ export const ImageRegistrySection = memo(function ImageRegistrySection() {
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleSave} disabled={saving}>
|
||||
<Save className="w-4 h-4 mr-1" />
|
||||
{saving ? '保存中...' : '保存镜像源'}
|
||||
{saving ? t('actions.saving') : t('registry.saveButton')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in new issue
Block a user