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, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
@@ -8,9 +9,10 @@ import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import i18n from '@/i18n'
|
||||
import type { LokiDeployment } from '@/types'
|
||||
|
||||
const schema = z.object({ yaml: z.string().trim().min(1, '请输入 YAML 配置') })
|
||||
const schema = z.object({ yaml: z.string().trim().min(1, i18n.t('deployments:retention.yamlRequired')) })
|
||||
type Values = z.infer<typeof schema>
|
||||
|
||||
interface Props {
|
||||
@@ -19,14 +21,14 @@ interface Props {
|
||||
onSaved: () => void
|
||||
}
|
||||
|
||||
function configLocation(deployment: LokiDeployment) {
|
||||
if (deployment.mode === 'k8s-helm') return 'Helm values.yaml → loki.limits_config.retention_period'
|
||||
if (deployment.mode === 'host-binary') return '/etc/loki/loki.yaml → limits_config.retention_period'
|
||||
if (deployment.mode === 'host-docker') return `主机 /etc/lokideploy/loki-${deployment.id}/loki.yaml → limits_config.retention_period`
|
||||
return `ConfigMap ${deployment.namespace || 'log'}/loki-config → loki.yaml`
|
||||
function configLocation(deployment: LokiDeployment, t: (k: string, o?: Record<string, unknown>) => string) {
|
||||
if (deployment.mode === 'host-binary') return t('retention.locationHostBinary')
|
||||
if (deployment.mode === 'host-docker') return t('retention.locationHostDocker', { id: deployment.id })
|
||||
return t('retention.locationK8s', { namespace: deployment.namespace || 'log' })
|
||||
}
|
||||
|
||||
export const RetentionDialog = memo(function RetentionDialog({ deployment, onClose, onSaved }: Props) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [liveConfig, setLiveConfig] = useState<{ location: string; yaml: string } | null>(null)
|
||||
const [configLoading, setConfigLoading] = useState(false)
|
||||
@@ -51,7 +53,7 @@ export const RetentionDialog = memo(function RetentionDialog({ deployment, onClo
|
||||
try {
|
||||
setLiveConfig(await deploymentApi.liveConfig(deployment.id))
|
||||
} catch (err) {
|
||||
setConfigError(err instanceof Error ? err.message : '读取运行配置失败')
|
||||
setConfigError(err instanceof Error ? err.message : t('retention.errLoad'))
|
||||
} finally {
|
||||
setConfigLoading(false)
|
||||
}
|
||||
@@ -64,9 +66,9 @@ export const RetentionDialog = memo(function RetentionDialog({ deployment, onClo
|
||||
const result = await deploymentApi.updateRetention(deployment.id, yaml, apply)
|
||||
onSaved()
|
||||
onClose()
|
||||
if (apply) toast.info(`已保存 ${result.retentionDays} 天,Loki 正在重新部署`)
|
||||
if (apply) toast.info(t('retention.savedApplied', { days: result.retentionDays }))
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : '保存 Loki 配置失败')
|
||||
toast.error(err instanceof Error ? err.message : t('retention.errSave'))
|
||||
onSaved()
|
||||
} finally {
|
||||
setSaving(false)
|
||||
@@ -77,23 +79,22 @@ export const RetentionDialog = memo(function RetentionDialog({ deployment, onClo
|
||||
<Dialog open={deployment !== null} onOpenChange={(open) => { if (!open && !saving) onClose() }}>
|
||||
<DialogContent className="sm:max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>编辑 Loki 配置 · {deployment?.name}</DialogTitle>
|
||||
<DialogTitle>{deployment ? t('retention.title', { name: deployment.name }) : ''}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-1 text-sm text-muted-foreground">
|
||||
<p>配置位置:{deployment ? configLocation(deployment) : ''}</p>
|
||||
<p>保留策略由 Loki compactor 执行;MinIO 只负责存储日志对象。</p>
|
||||
<p>{t('retention.locationLabel')}{deployment ? configLocation(deployment, t) : ''}</p>
|
||||
<p>{t('retention.compactorNote')}</p>
|
||||
</div>
|
||||
<div className="space-y-2 rounded-md border border-border p-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium">运行中的完整 loki.yaml</p>
|
||||
<p className="text-xs text-muted-foreground">从部署目标读取,存储凭证已遮盖;下方保留天数仍单独编辑。</p>
|
||||
<p className="text-sm font-medium">{t('retention.liveTitle')}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('retention.liveHint')}</p>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={loadConfig} disabled={configLoading || deployment?.mode === 'k8s-helm'}>
|
||||
{configLoading ? '读取中…' : liveConfig ? '刷新配置' : '查看完整配置'}
|
||||
<Button type="button" variant="outline" size="sm" onClick={loadConfig} disabled={configLoading}>
|
||||
{configLoading ? t('retention.loading') : liveConfig ? t('retention.refresh') : t('retention.view')}
|
||||
</Button>
|
||||
</div>
|
||||
{deployment?.mode === 'k8s-helm' && <p className="text-xs text-muted-foreground">Helm 模式暂不支持读取运行配置。</p>}
|
||||
{configError && <p className="text-sm text-destructive">{configError}</p>}
|
||||
{liveConfig && (
|
||||
<div>
|
||||
@@ -103,7 +104,7 @@ export const RetentionDialog = memo(function RetentionDialog({ deployment, onClo
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-md border border-border bg-muted/40 p-3 text-xs">
|
||||
<p className="mb-1 font-medium text-foreground">由部署模板生成</p>
|
||||
<p className="mb-1 font-medium text-foreground">{t('retention.templateNote')}</p>
|
||||
<pre className="text-muted-foreground">{'compactor:\n retention_enabled: true\n retention_delete_delay: 2h'}</pre>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
@@ -113,28 +114,24 @@ export const RetentionDialog = memo(function RetentionDialog({ deployment, onClo
|
||||
name="yaml"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>保留配置 YAML</FormLabel>
|
||||
<FormLabel>{t('retention.yamlLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={4} spellCheck={false} className="font-mono text-sm" {...field} />
|
||||
</FormControl>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
编辑 limits_config.retention_period,例如 90d 或 2160h。此处只接受这一项配置。
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">{t('retention.yamlHint')}</p>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{deployment?.status === 'running'
|
||||
? '仅保存会更新部署记录;保存并应用会重新部署 Loki,查询可能短暂中断。'
|
||||
: '保存后在下次部署时生效。'}
|
||||
{deployment?.status === 'running' ? t('retention.applyHintRunning') : t('retention.applyHintIdle')}
|
||||
</p>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={onClose} disabled={saving}>取消</Button>
|
||||
<Button type="submit" variant="outline" disabled={saving}>仅保存</Button>
|
||||
<Button type="button" variant="outline" onClick={onClose} disabled={saving}>{t('retention.cancel')}</Button>
|
||||
<Button type="submit" variant="outline" disabled={saving}>{t('retention.saveOnly')}</Button>
|
||||
{deployment?.status === 'running' && (
|
||||
<Button type="button" onClick={save(true)} disabled={saving}>
|
||||
{saving ? '处理中…' : '保存并应用'}
|
||||
{saving ? t('retention.processing') : t('retention.saveApply')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user