feat(ui): traffic tooltip (sess/bytes) auf Backend-Health-Chips

Backends- und Routing-Rules-Seite zeigen jetzt beim Hover auf den
UP/DOWN-Chip die aggregierten HAProxy-Stats des jeweiligen Backends:
aktive Sessions + kumulierte Bytes rein/raus seit letztem HAProxy-Start.
Daten kommen aus dem bereits vorhandenen /haproxy/stats-Endpunkt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-20 07:21:16 +02:00
parent 20f3a4fe26
commit e4b66cfcac
3 changed files with 54 additions and 11 deletions

View File

@@ -1,7 +1,7 @@
import { useState } from 'react'
import {
Alert, Button, Card, Form, Input, InputNumber, Modal, Popconfirm,
Select, Space, Switch, Table, Tag, Typography, message,
Select, Space, Switch, Table, Tag, Tooltip, Typography, message,
} from 'antd'
import type { ColumnsType } from 'antd/es/table'
import { DatabaseOutlined, PlusOutlined } from '@ant-design/icons'
@@ -86,7 +86,17 @@ async function listDomains(): Promise<DomainFull[]> {
return (r.data.data as { domains?: DomainFull[] }).domains ?? []
}
interface HAProxyStat { backend: string; server: string; status: string }
interface HAProxyStat {
backend: string; server: string; status: string
sessions: number; bytes_in: number; bytes_out: number
}
function fmtBytes(n: number): string {
if (n >= 1_073_741_824) return (n / 1_073_741_824).toFixed(1) + ' GB'
if (n >= 1_048_576) return (n / 1_048_576).toFixed(1) + ' MB'
if (n >= 1_024) return (n / 1_024).toFixed(0) + ' KB'
return n + ' B'
}
async function listHAProxyStats(): Promise<HAProxyStat[]> {
try {
const r = await apiClient.get('/haproxy/stats')
@@ -118,6 +128,16 @@ export default function BackendsPage() {
return 'DOWN'
}
const backendTraffic = (id: number) => {
const servers = (haproxyStats ?? []).filter(s => s.backend === `eg_backend_${id}`)
if (!servers.length) return null
return {
sessions: servers.reduce((a, s) => a + (s.sessions ?? 0), 0),
bytesIn: servers.reduce((a, s) => a + (s.bytes_in ?? 0), 0),
bytesOut: servers.reduce((a, s) => a + (s.bytes_out ?? 0), 0),
}
}
// server-counts pro Backend laden wir lazy bei Expansion; in der
// Tabelle reicht ein Hinweis ob 0 / N Server.
const { data: serverCounts } = useQuery({
@@ -243,7 +263,15 @@ export default function BackendsPage() {
const s = backendLiveStatus(row.id)
if (!s) return <Text type="secondary" style={{ fontSize: 12 }}></Text>
const color = s === 'UP' ? 'green' : s === 'DEGRADED' ? 'orange' : 'red'
return <Tag color={color} style={{ margin: 0 }}>{s}</Tag>
const tr = backendTraffic(row.id)
const tip = tr
? `${tr.sessions} sess · ↓${fmtBytes(tr.bytesIn)}${fmtBytes(tr.bytesOut)}`
: undefined
return (
<Tooltip title={tip}>
<Tag color={color} style={{ margin: 0 }}>{s}</Tag>
</Tooltip>
)
},
},
{ title: t('backends.active'), dataIndex: 'active', key: 'active', render: (v: boolean) => <StatusDot active={v} /> },