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:
1 parent
f9c6c1d826
commit
2d73661dd1
127 files changed
+8117
-2876
No files matched your search
@@ -0,0 +1,146 @@
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
import { toast } from 'sonner'
|
||||
import { deploymentApi } from '@/api'
|
||||
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 type { LokiDeployment } from '@/types'
|
||||
|
||||
const schema = z.object({ yaml: z.string().trim().min(1, '请输入 YAML 配置') })
|
||||
type Values = z.infer<typeof schema>
|
||||
|
||||
interface Props {
|
||||
deployment: LokiDeployment | null
|
||||
onClose: () => void
|
||||
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`
|
||||
}
|
||||
|
||||
export const RetentionDialog = memo(function RetentionDialog({ deployment, onClose, onSaved }: Props) {
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [liveConfig, setLiveConfig] = useState<{ location: string; yaml: string } | null>(null)
|
||||
const [configLoading, setConfigLoading] = useState(false)
|
||||
const [configError, setConfigError] = useState('')
|
||||
const form = useForm<Values>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: { yaml: 'limits_config:\n retention_period: 30d\n' },
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (deployment) {
|
||||
form.reset({ yaml: `limits_config:\n retention_period: ${deployment.retentionDays || 30}d\n` })
|
||||
}
|
||||
setLiveConfig(null)
|
||||
setConfigError('')
|
||||
}, [deployment, form])
|
||||
|
||||
const loadConfig = async () => {
|
||||
if (!deployment) return
|
||||
setConfigLoading(true)
|
||||
setConfigError('')
|
||||
try {
|
||||
setLiveConfig(await deploymentApi.liveConfig(deployment.id))
|
||||
} catch (err) {
|
||||
setConfigError(err instanceof Error ? err.message : '读取运行配置失败')
|
||||
} finally {
|
||||
setConfigLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const save = (apply: boolean) => form.handleSubmit(async ({ yaml }) => {
|
||||
if (!deployment) return
|
||||
setSaving(true)
|
||||
try {
|
||||
const result = await deploymentApi.updateRetention(deployment.id, yaml, apply)
|
||||
onSaved()
|
||||
onClose()
|
||||
if (apply) toast.info(`已保存 ${result.retentionDays} 天,Loki 正在重新部署`)
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : '保存 Loki 配置失败')
|
||||
onSaved()
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<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>
|
||||
</DialogHeader>
|
||||
<div className="space-y-1 text-sm text-muted-foreground">
|
||||
<p>配置位置:{deployment ? configLocation(deployment) : ''}</p>
|
||||
<p>保留策略由 Loki compactor 执行;MinIO 只负责存储日志对象。</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>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={loadConfig} disabled={configLoading || deployment?.mode === 'k8s-helm'}>
|
||||
{configLoading ? '读取中…' : liveConfig ? '刷新配置' : '查看完整配置'}
|
||||
</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>
|
||||
<p className="mb-2 text-xs text-muted-foreground">{liveConfig.location}</p>
|
||||
<pre className="max-h-[45vh] overflow-auto rounded-md bg-muted/50 p-3 font-mono text-xs whitespace-pre">{liveConfig.yaml}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-md border border-border bg-muted/40 p-3 text-xs">
|
||||
<p className="mb-1 font-medium text-foreground">由部署模板生成</p>
|
||||
<pre className="text-muted-foreground">{'compactor:\n retention_enabled: true\n retention_delete_delay: 2h'}</pre>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<form onSubmit={save(false)} className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="yaml"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>保留配置 YAML</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>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{deployment?.status === 'running'
|
||||
? '仅保存会更新部署记录;保存并应用会重新部署 Loki,查询可能短暂中断。'
|
||||
: '保存后在下次部署时生效。'}
|
||||
</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>
|
||||
{deployment?.status === 'running' && (
|
||||
<Button type="button" onClick={save(true)} disabled={saving}>
|
||||
{saving ? '处理中…' : '保存并应用'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
})
|
||||
Reference in new issue
Block a user