diff --git a/VERSION b/VERSION index 2c69d9f..633becb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.43 +1.1.44 diff --git a/management-ui/src/pages/Backends/index.tsx b/management-ui/src/pages/Backends/index.tsx index 4d2f17f..b30a4c7 100644 --- a/management-ui/src/pages/Backends/index.tsx +++ b/management-ui/src/pages/Backends/index.tsx @@ -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 { 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 { 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 const color = s === 'UP' ? 'green' : s === 'DEGRADED' ? 'orange' : 'red' - return {s} + const tr = backendTraffic(row.id) + const tip = tr + ? `${tr.sessions} sess · ↓${fmtBytes(tr.bytesIn)} ↑${fmtBytes(tr.bytesOut)}` + : undefined + return ( + + {s} + + ) }, }, { title: t('backends.active'), dataIndex: 'active', key: 'active', render: (v: boolean) => }, diff --git a/management-ui/src/pages/RoutingRules/index.tsx b/management-ui/src/pages/RoutingRules/index.tsx index 8cbb67b..1f9fc99 100644 --- a/management-ui/src/pages/RoutingRules/index.tsx +++ b/management-ui/src/pages/RoutingRules/index.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { Button, Form, Input, InputNumber, Modal, Select, Space, Switch, Tag, message } from 'antd' +import { Button, Form, Input, InputNumber, Modal, Select, Space, Switch, Tag, Tooltip, 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,7 +50,14 @@ async function listBackends(): Promise { return (r.data.data as { backends?: Backend[] }).backends ?? [] } -interface HAProxyStat { backend: string; status: string } +interface HAProxyStat { backend: 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 { try { const r = await apiClient.get('/haproxy/stats') @@ -77,11 +84,15 @@ export default function RoutingRulesPage() { const b = backends?.find((x) => x.id === id) return b ? `${b.name} (${b.address}:${b.port})` : `#${id}` } - const backendHealth = (id: number): 'UP' | 'DOWN' | null => { + const backendHealth = (id: number) => { 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 up = servers.some(s => s.status === 'UP') + const sessions = servers.reduce((a, s) => a + (s.sessions ?? 0), 0) + const bytesIn = servers.reduce((a, s) => a + (s.bytes_in ?? 0), 0) + const bytesOut = servers.reduce((a, s) => a + (s.bytes_out ?? 0), 0) + return { up, sessions, bytesIn, bytesOut } } const [editing, setEditing] = useState(null) @@ -119,12 +130,16 @@ export default function RoutingRulesPage() { { title: t('routing.backend'), dataIndex: 'backend_id', key: 'backend', render: (id: number) => { - const health = backendHealth(id) + const h = backendHealth(id) + const tip = h ? `${h.sessions} sess · ↓${fmtBytes(h.bytesIn)} ↑${fmtBytes(h.bytesOut)}` : undefined return ( {backendLabel(id)} - {health === 'UP' && UP} - {health === 'DOWN' && DOWN} + {h && ( + + {h.up ? 'UP' : 'DOWN'} + + )} ) },