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,81 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { logsApi } from '@/api/logs'
|
||||
import { applyLabelFilter, extractSelector } from '../query-utils'
|
||||
|
||||
type Template = 'app' | 'error' | 'warn' | 'pod' | 'ingress'
|
||||
|
||||
export function QuickQueryTemplates({ sourceId, query, onQueryChange, compact }: {
|
||||
sourceId: number
|
||||
query: string
|
||||
onQueryChange: (query: string) => void
|
||||
compact?: boolean
|
||||
}) {
|
||||
const [app, setApp] = useState('')
|
||||
const [namespace, setNamespace] = useState('')
|
||||
const [pod, setPod] = useState('')
|
||||
const [apps, setApps] = useState<string[]>([])
|
||||
const [namespaces, setNamespaces] = useState<string[]>([])
|
||||
const [pods, setPods] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (!sourceId) return
|
||||
let active = true
|
||||
Promise.allSettled([
|
||||
logsApi.labelValues(sourceId, 'app'),
|
||||
logsApi.labelValues(sourceId, 'namespace'),
|
||||
logsApi.labelValues(sourceId, 'pod'),
|
||||
]).then(([nextApps, nextNamespaces, nextPods]) => {
|
||||
if (active) {
|
||||
setApps(nextApps.status === 'fulfilled' ? nextApps.value : [])
|
||||
setNamespaces(nextNamespaces.status === 'fulfilled' ? nextNamespaces.value : [])
|
||||
setPods(nextPods.status === 'fulfilled' ? nextPods.value : [])
|
||||
}
|
||||
})
|
||||
return () => { active = false }
|
||||
}, [sourceId])
|
||||
|
||||
function generate(template: Template) {
|
||||
let next = extractSelector(query) || '{}'
|
||||
if (template === 'ingress') next = applyLabelFilter(next, 'namespace', namespace.trim() || 'ingress-nginx')
|
||||
else if (namespace.trim()) next = applyLabelFilter(next, 'namespace', namespace.trim())
|
||||
if (app.trim()) next = applyLabelFilter(next, 'app', app.trim())
|
||||
if (pod.trim()) next = applyLabelFilter(next, 'pod', pod.trim())
|
||||
if (next === '{}') return
|
||||
if (template === 'error') next += ' |= "error"'
|
||||
if (template === 'warn') next += ' |= "warn"'
|
||||
if (template === 'ingress') next += ' |= "HTTP/"'
|
||||
onQueryChange(next)
|
||||
}
|
||||
|
||||
const hasSelector = extractSelector(query).replace(/[{}\s]/g, '').length > 0
|
||||
const hasScope = Boolean(app.trim() || namespace.trim() || pod.trim() || hasSelector)
|
||||
|
||||
return <section className="space-y-2" aria-label="快捷查询模板">
|
||||
{!compact && <h2 className="text-sm font-medium">快捷生成 LogQL</h2>}
|
||||
{!compact && <p className="text-xs text-muted-foreground">选 app、namespace 或副本,再生成可编辑的 LogQL。</p>}
|
||||
<div className="flex flex-wrap items-end gap-2">
|
||||
<label className="min-w-36 flex-1 text-xs text-muted-foreground">app
|
||||
<Input className="mt-1 h-8" list="quick-log-apps" value={app} onChange={event => setApp(event.target.value)} placeholder="搜索或输入 app" />
|
||||
</label>
|
||||
<datalist id="quick-log-apps">{apps.map(value => <option value={value} key={value} />)}</datalist>
|
||||
<label className="min-w-36 flex-1 text-xs text-muted-foreground">namespace
|
||||
<Input className="mt-1 h-8" list="quick-log-namespaces" value={namespace} onChange={event => setNamespace(event.target.value)} placeholder="可选" />
|
||||
</label>
|
||||
<datalist id="quick-log-namespaces">{namespaces.map(value => <option value={value} key={value} />)}</datalist>
|
||||
<label className="min-w-36 flex-1 text-xs text-muted-foreground">副本 pod
|
||||
<Input className="mt-1 h-8" list="quick-log-pods" value={pod} onChange={event => setPod(event.target.value)} placeholder="可选" />
|
||||
</label>
|
||||
<datalist id="quick-log-pods">{pods.map(value => <option value={value} key={value} />)}</datalist>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<Button size="sm" variant="outline" disabled={!hasScope} onClick={() => generate('app')}>查询该范围日志</Button>
|
||||
<Button size="sm" variant="outline" disabled={!hasScope} onClick={() => generate('error')}>错误 error</Button>
|
||||
<Button size="sm" variant="outline" disabled={!hasScope} onClick={() => generate('warn')}>告警 warn</Button>
|
||||
<Button size="sm" variant="outline" disabled={!pod.trim()} onClick={() => generate('pod')}>指定副本日志</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => generate('ingress')}>Ingress 访问日志</Button>
|
||||
{!hasScope && <span className="self-center text-xs text-muted-foreground">先填写 app、namespace 或 pod,也可在 Label 浏览器选值</span>}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
Reference in new issue
Block a user