feat(dashboard): show HAProxy check_status code on DOWN servers

When a backend server is DOWN, the HAProxy card now shows the check_status
code (L4CON, L4TOUT, L7STS, L6CON, …) as a volcano-colored tag next to
the DOWN badge. Hovering shows a plain-English explanation of the failure
reason so operators can diagnose without leaving the dashboard. v1.1.116.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-27 17:08:34 +02:00
parent 1c60affd27
commit 7f4606ba16
5 changed files with 25 additions and 4 deletions

View File

@@ -1 +1 @@
1.1.115 1.1.116

View File

@@ -60,7 +60,7 @@ import (
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users" usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
) )
var version = "1.1.115" var version = "1.1.116"
func main() { func main() {
addr := os.Getenv("EDGEGUARD_API_ADDR") addr := os.Getenv("EDGEGUARD_API_ADDR")

View File

@@ -11,7 +11,7 @@ import (
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup" "git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
) )
var version = "1.1.115" var version = "1.1.116"
const usage = `edgeguard-ctl — EdgeGuard CLI const usage = `edgeguard-ctl — EdgeGuard CLI

View File

@@ -41,7 +41,7 @@ import (
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
) )
var version = "1.1.115" var version = "1.1.116"
const ( const (
// renewTickInterval — how often we re-evaluate expiring certs. // renewTickInterval — how often we re-evaluate expiring certs.

View File

@@ -145,6 +145,22 @@ function relativeTime(unix: number, t: (k: string, v?: Record<string, unknown>)
if (sec < 86400) return t('common.relTime.Xh', { n: Math.floor(sec / 3600) }) if (sec < 86400) return t('common.relTime.Xh', { n: Math.floor(sec / 3600) })
return t('common.relTime.Xd', { n: Math.floor(sec / 86400) }) return t('common.relTime.Xd', { n: Math.floor(sec / 86400) })
} }
function haCheckHint(code: string): string {
const hints: Record<string, string> = {
L4CON: 'TCP connection refused — server port closed or firewall blocking',
L4TOUT: 'TCP connection timeout — server unreachable (routing/firewall/server down)',
L6CON: 'SSL/TLS handshake failed — certificate or ALPN mismatch',
L6TOUT: 'SSL/TLS timeout',
L6RSP: 'SSL/TLS protocol error',
L7STS: 'HTTP check: unexpected status code (health-check path returns non-2xx)',
L7RSP: 'HTTP check: unexpected response from server',
L7TOUT: 'HTTP check: timeout waiting for response',
SOCKERR: 'Socket error — OS-level problem (EAGAIN, ECONNRESET …)',
INI: 'Check not yet performed since last reload',
}
return hints[code] ?? code
}
function formatUptime(sec: number): string { function formatUptime(sec: number): string {
if (sec < 60) return `${sec}s` if (sec < 60) return `${sec}s`
const days = Math.floor(sec / 86400) const days = Math.floor(sec / 86400)
@@ -515,6 +531,11 @@ export default function DashboardPage() {
color={b.status === 'UP' ? 'green' : b.status === 'no check' ? 'default' : 'red'} color={b.status === 'UP' ? 'green' : b.status === 'no check' ? 'default' : 'red'}
style={{ margin: 0, fontSize: 10 }} style={{ margin: 0, fontSize: 10 }}
>{b.status}</Tag> >{b.status}</Tag>
{b.health && b.health !== 'L7OK' && b.health !== 'L4OK' && (
<Tooltip title={haCheckHint(b.health)}>
<Tag color="volcano" style={{ margin: 0, fontSize: 10, cursor: 'help' }}>{b.health}</Tag>
</Tooltip>
)}
<Text type="secondary" style={{ fontSize: 11 }}> <Text type="secondary" style={{ fontSize: 11 }}>
{b.sessions} sess{b.req_rate > 0 ? ` · ${b.req_rate}/s` : ''} · {formatBytes(b.bytes_in)} {formatBytes(b.bytes_out)} {b.sessions} sess{b.req_rate > 0 ? ` · ${b.req_rate}/s` : ''} · {formatBytes(b.bytes_in)} {formatBytes(b.bytes_out)}
</Text> </Text>