feat: K8s 部署清单、Token 接入加固与日志/设置页优化
- kubernetes/: 30Gi PVC、单副本 Recreate、Secret/Service/NodePort/Ingress 清单与部署文档 - 数据安全: kubeconfig/token 统一加密存储,启动迁移历史明文,DB 0600 - Token 接入: 命名空间级最小权限提示、长期 Token、自签证书 TLS 兼容 - 部署: 命名空间级 Token 下跳过 Namespace patch/枚举,节点信息容错 - 日志页: namespace → app → pod 选择顺序与作用域收窄 - 设置页: 拆分为通用/告警/LDAP/镜像源四个 tab - 弹框: 统一 max-h 滚动与长命令换行,修复内容溢出
This commit is contained in:
1 parent
2d73661dd1
commit
9f7c522157
50 files changed
+1346
-149
No files matched your search
@@ -6,36 +6,69 @@ import { applyLabelFilter, extractSelector } from '../query-utils'
|
||||
|
||||
type Template = 'app' | 'error' | 'warn' | 'pod' | 'ingress'
|
||||
|
||||
function selectorOf(labels: Record<string, string>) {
|
||||
const entries = Object.entries(labels).filter(([, value]) => value.trim())
|
||||
if (entries.length === 0) return ''
|
||||
return `{${entries.map(([key, value]) => `${key}=${JSON.stringify(value.trim())}`).join(',')}}`
|
||||
}
|
||||
|
||||
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 [app, setApp] = useState('')
|
||||
const [pod, setPod] = useState('')
|
||||
const [apps, setApps] = useState<string[]>([])
|
||||
const [namespaces, setNamespaces] = useState<string[]>([])
|
||||
const [apps, setApps] = useState<string[]>([])
|
||||
const [pods, setPods] = useState<string[]>([])
|
||||
|
||||
// namespace 列表只加载一次
|
||||
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 : [])
|
||||
}
|
||||
})
|
||||
logsApi.labelValues(sourceId, 'namespace')
|
||||
.then(next => { if (active) setNamespaces(next) })
|
||||
.catch(() => { if (active) setNamespaces([]) })
|
||||
return () => { active = false }
|
||||
}, [sourceId])
|
||||
|
||||
// app 候选:选了 namespace 就只在该命名空间内搜索
|
||||
useEffect(() => {
|
||||
if (!sourceId) return
|
||||
let active = true
|
||||
const selector = selectorOf({ namespace })
|
||||
logsApi.labelValues(sourceId, 'app', selector)
|
||||
.then(next => { if (active) setApps(next) })
|
||||
.catch(() => { if (active) setApps([]) })
|
||||
return () => { active = false }
|
||||
}, [sourceId, namespace])
|
||||
|
||||
// pod 候选:按 namespace + app 收窄
|
||||
useEffect(() => {
|
||||
if (!sourceId) return
|
||||
let active = true
|
||||
const selector = selectorOf({ namespace, app })
|
||||
logsApi.labelValues(sourceId, 'pod', selector)
|
||||
.then(next => { if (active) setPods(next) })
|
||||
.catch(() => { if (active) setPods([]) })
|
||||
return () => { active = false }
|
||||
}, [sourceId, namespace, app])
|
||||
|
||||
function chooseNamespace(value: string) {
|
||||
setNamespace(value)
|
||||
// 换命名空间后旧的 app / pod 条件失效
|
||||
setApp('')
|
||||
setPod('')
|
||||
}
|
||||
|
||||
function chooseApp(value: string) {
|
||||
setApp(value)
|
||||
setPod('')
|
||||
}
|
||||
|
||||
function generate(template: Template) {
|
||||
let next = extractSelector(query) || '{}'
|
||||
if (template === 'ingress') next = applyLabelFilter(next, 'namespace', namespace.trim() || 'ingress-nginx')
|
||||
@@ -54,18 +87,18 @@ export function QuickQueryTemplates({ sourceId, query, onQueryChange, compact }:
|
||||
|
||||
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>}
|
||||
{!compact && <p className="text-xs text-muted-foreground">先选 namespace,再选 app,最后选副本 pod;app 候选只来自所选命名空间。</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="可选" />
|
||||
<Input className="mt-1 h-8" list="quick-log-namespaces" value={namespace} onChange={event => chooseNamespace(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">app
|
||||
<Input className="mt-1 h-8" list="quick-log-apps" value={app} onChange={event => chooseApp(event.target.value)} placeholder={namespace ? '搜索该命名空间下的 app' : '先选 namespace 可缩小范围'} />
|
||||
</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">副本 pod
|
||||
<Input className="mt-1 h-8" list="quick-log-pods" value={pod} onChange={event => setPod(event.target.value)} placeholder="可选" />
|
||||
<Input className="mt-1 h-8" list="quick-log-pods" value={pod} onChange={event => setPod(event.target.value)} placeholder={app ? '搜索该 app 的副本' : '可选'} />
|
||||
</label>
|
||||
<datalist id="quick-log-pods">{pods.map(value => <option value={value} key={value} />)}</datalist>
|
||||
</div>
|
||||
@@ -75,7 +108,7 @@ export function QuickQueryTemplates({ sourceId, query, onQueryChange, compact }:
|
||||
<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>}
|
||||
{!hasScope && <span className="self-center text-xs text-muted-foreground">先选 namespace,再选 app 或 pod,也可在 Label 浏览器选值</span>}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
Reference in new issue
Block a user