fix(status): keep non-HTTP services grey instead of red

Per-service checks now only probe HTTP(S)-reachable services. SSH (22) and
other non-web ports (DB, mail, DNS, raw TCP) stay 'unknown' (grey category
colour) rather than going red — an open TCP socket doesn't prove the service
is healthy, and a firewalled port flapped red misleadingly.

ha-relevant: yes
This commit is contained in:
Pouzor
2026-06-10 10:48:03 +02:00
parent 2db173cd56
commit ed310f0d8d
2 changed files with 43 additions and 37 deletions
+24 -23
View File
@@ -122,10 +122,12 @@ async def _tcp_connect(host: str, port: int) -> bool:
# --- Per-service status checks ---
# Ports that are definitely not HTTP/web — mirror of frontend serviceUrl.ts.
# SSH (22) is handled as a plain TCP check, so it is intentionally absent here.
# Ports that are not HTTP/web. These get NO status check — a service here stays
# grey (unknown) rather than going red. An open TCP socket doesn't prove the
# service is healthy, and a closed one flaps red misleadingly (e.g. SSH on a
# box that simply firewalls 22). Only HTTP(S)-reachable services are checked.
_NON_HTTP_PORTS = frozenset({
21, 23, 25, 465, 587, 53, 110, 143, 993, 995, 389, 636, 445, 514,
22, 21, 23, 25, 465, 587, 53, 110, 143, 993, 995, 389, 636, 445, 514,
1433, 3306, 5432, 5672, 6379, 9092, 11211, 27017, 27018,
})
_HTTPS_PORTS = frozenset({443, 8443})
@@ -139,9 +141,10 @@ def _service_host(svc: dict[str, Any], host: str) -> str:
async def check_service(svc: dict[str, Any], host: str | None) -> str:
"""Check a single service. Returns 'online' | 'offline' | 'unknown'.
Web services get an HTTP(S) GET; everything else with a port gets a TCP
connect. UDP services and port-less non-web services are 'unknown' so they
keep their category colour rather than flashing red.
Only HTTP(S)-reachable services get a real check (an HTTP GET). Everything
else — SSH, databases, mail, DNS, raw TCP, UDP, port-less — stays 'unknown'
so it keeps its category colour instead of flashing red. An open TCP socket
doesn't prove a non-web service is healthy, so we don't pretend it does.
"""
if not host or host.startswith("-"):
return "unknown"
@@ -151,24 +154,22 @@ async def check_service(svc: dict[str, Any], host: str | None) -> str:
port = svc.get("port")
port = int(port) if isinstance(port, int) or (isinstance(port, str) and port.isdigit()) else None
try:
if port is not None and port in _NON_HTTP_PORTS:
return "online" if await _tcp_connect(host, port) else "offline"
name = str(svc.get("service_name", "")).lower()
is_web = port is None or port not in _NON_HTTP_PORTS
if is_web and (port is not None or "http" in name):
scheme = "https" if (
port in _HTTPS_PORTS or "https" in name or "ssl" in name or "tls" in name
) else "http"
url_host = _service_host(svc, host)
url = f"{scheme}://{url_host}" + (f":{port}" if port is not None else "")
return "online" if await _http_get(url, verify=False) else "offline"
if port is not None:
return "online" if await _tcp_connect(host, port) else "offline"
# Non-HTTP ports (SSH 22, DB, mail, …) are never checked — keep them grey.
if port is not None and port in _NON_HTTP_PORTS:
return "unknown"
name = str(svc.get("service_name", "")).lower()
is_web = port is not None or "http" in name
if not is_web:
return "unknown"
try:
scheme = "https" if (
port in _HTTPS_PORTS or "https" in name or "ssl" in name or "tls" in name
) else "http"
url_host = _service_host(svc, host)
url = f"{scheme}://{url_host}" + (f":{port}" if port is not None else "")
return "online" if await _http_get(url, verify=False) else "offline"
except Exception as exc:
logger.debug("Service check failed for %s:%s (%s)", host, port, exc)
return "offline"