feat(ui): HAProxy-Health-Chip in Routing-Rules-Tabelle
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { Button, Form, Input, InputNumber, Modal, Select, Switch, message } from 'antd'
|
||||
import { Button, Form, Input, InputNumber, Modal, Select, Space, Switch, Tag, message } from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { BranchesOutlined, PlusOutlined } from '@ant-design/icons'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
@@ -50,6 +50,15 @@ async function listBackends(): Promise<Backend[]> {
|
||||
return (r.data.data as { backends?: Backend[] }).backends ?? []
|
||||
}
|
||||
|
||||
interface HAProxyStat { backend: string; status: string }
|
||||
async function listHAProxyStats(): Promise<HAProxyStat[]> {
|
||||
try {
|
||||
const r = await apiClient.get('/haproxy/stats')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
return (r.data.data as { backends?: HAProxyStat[] }).backends ?? []
|
||||
} catch { return [] }
|
||||
}
|
||||
|
||||
export default function RoutingRulesPage() {
|
||||
const { t } = useTranslation()
|
||||
const qc = useQueryClient()
|
||||
@@ -57,12 +66,23 @@ export default function RoutingRulesPage() {
|
||||
const { data: rules, isLoading } = useQuery({ queryKey: ['routing-rules'], queryFn: listRules })
|
||||
const { data: domains } = useQuery({ queryKey: ['domains'], queryFn: listDomains })
|
||||
const { data: backends } = useQuery({ queryKey: ['backends'], queryFn: listBackends })
|
||||
const { data: haproxyStats } = useQuery({
|
||||
queryKey: ['haproxy', 'stats'],
|
||||
queryFn: listHAProxyStats,
|
||||
refetchInterval: 15_000,
|
||||
})
|
||||
|
||||
const domainName = (id: number) => domains?.find((d) => d.id === id)?.name ?? `#${id}`
|
||||
const backendLabel = (id: number) => {
|
||||
const b = backends?.find((x) => x.id === id)
|
||||
return b ? `${b.name} (${b.address}:${b.port})` : `#${id}`
|
||||
}
|
||||
const backendHealth = (id: number): 'UP' | 'DOWN' | null => {
|
||||
if (!haproxyStats?.length) return null
|
||||
const servers = haproxyStats.filter(s => s.backend === `eg_backend_${id}`)
|
||||
if (!servers.length) return null
|
||||
return servers.some(s => s.status === 'UP') ? 'UP' : 'DOWN'
|
||||
}
|
||||
|
||||
const [editing, setEditing] = useState<RoutingRule | null>(null)
|
||||
const [creating, setCreating] = useState(false)
|
||||
@@ -96,7 +116,19 @@ export default function RoutingRulesPage() {
|
||||
const columns: ColumnsType<RoutingRule> = [
|
||||
{ title: t('routing.domain'), dataIndex: 'domain_id', key: 'domain', render: (id: number) => domainName(id) },
|
||||
{ title: t('routing.pathPrefix'), dataIndex: 'path_prefix', key: 'path' },
|
||||
{ title: t('routing.backend'), dataIndex: 'backend_id', key: 'backend', render: (id: number) => backendLabel(id) },
|
||||
{
|
||||
title: t('routing.backend'), dataIndex: 'backend_id', key: 'backend',
|
||||
render: (id: number) => {
|
||||
const health = backendHealth(id)
|
||||
return (
|
||||
<Space size={4}>
|
||||
<span>{backendLabel(id)}</span>
|
||||
{health === 'UP' && <Tag color="green" style={{ margin: 0 }}>UP</Tag>}
|
||||
{health === 'DOWN' && <Tag color="red" style={{ margin: 0 }}>DOWN</Tag>}
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
},
|
||||
{ title: t('routing.priority'), dataIndex: 'priority', key: 'priority' },
|
||||
{ title: t('routing.active'), dataIndex: 'active', key: 'active', render: (v: boolean) => <StatusDot active={v} /> },
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user