feat(server): host presence, absent state and host-level status
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uc9V1v7iXruubpoLiSE24M
This commit is contained in:
+45
-1
@@ -29,6 +29,7 @@ class Connection:
|
||||
UP = "up"
|
||||
DOWN = "down"
|
||||
OVERDUE = "overdue"
|
||||
ABSENT = "absent"
|
||||
|
||||
def __init__(self, host, cid, addr, afam):
|
||||
self.host = host
|
||||
@@ -94,7 +95,9 @@ class Connection:
|
||||
now = time.time()
|
||||
if not Null:
|
||||
d["addr"] = self.addr
|
||||
if self.rtts[-1]:
|
||||
if self.state == Connection.ABSENT:
|
||||
d["rtt"] = ""
|
||||
elif self.rtts[-1]:
|
||||
d["rtt"] = "%d" % round(self.rtts[-1])
|
||||
elif self.state == Connection.UNKNOWN:
|
||||
d["rtt"] = ""
|
||||
@@ -299,6 +302,8 @@ class Host:
|
||||
self.alert_states = {}
|
||||
# Stale-data timers: {plugin_name: asyncio.TimerHandle}
|
||||
self.plugin_timers = {}
|
||||
# Client-reported protocol presence: {afam: {"present": bool, "ts": float}}
|
||||
self.presence: dict = {}
|
||||
# User access control
|
||||
self.owner: str | None = None # username of owner
|
||||
self.managers: list = [] # usernames with manager role
|
||||
@@ -322,6 +327,7 @@ class Host:
|
||||
d["name"] = "<b>%s</b>" % d["name"]
|
||||
d["dyn"] = str(self.dyn)
|
||||
d["num"] = self.num
|
||||
d["status"] = self.status()
|
||||
|
||||
# Add alert counts (split by acknowledged status)
|
||||
warning_unacked = 0
|
||||
@@ -444,6 +450,7 @@ class Host:
|
||||
_, os_data = latest_os
|
||||
hbc_version = os_data.get("hbc_version")
|
||||
ddict["hbc_version"] = hbc_version
|
||||
ddict["status"] = self.status()
|
||||
|
||||
return ddict
|
||||
|
||||
@@ -459,6 +466,41 @@ class Host:
|
||||
else:
|
||||
return addr.find(".") > 0
|
||||
|
||||
def set_presence(self, afam, present, now):
|
||||
"""Record the client's report of whether it has internet connectivity on afam."""
|
||||
self.presence[afam] = {"present": bool(present), "ts": now}
|
||||
|
||||
def flag_present(self, afam):
|
||||
"""Client-reported presence; True when the client never reported (legacy clients)."""
|
||||
entry = getattr(self, "presence", {}).get(afam)
|
||||
return True if entry is None else entry["present"]
|
||||
|
||||
def family_present(self, afam):
|
||||
"""True if afam should be monitored: the client says it is present, or heartbeats
|
||||
are demonstrably arriving on it (real traffic overrides a false "absent")."""
|
||||
conn = self.connections.get(afam)
|
||||
return self.flag_present(afam) or (conn is not None and conn.state == Connection.UP)
|
||||
|
||||
def status(self):
|
||||
"""Host-level status: down, overdue, simplex, up or unknown.
|
||||
|
||||
overdue: every present family is overdue. simplex: both present, one up, one overdue
|
||||
(same severity as up). Families the client reports absent are not considered.
|
||||
"""
|
||||
conns = self.connections
|
||||
if any(c.state == Connection.DOWN for c in conns.values()):
|
||||
return "down"
|
||||
present = [c for afam, c in conns.items() if self.family_present(afam)]
|
||||
up = sum(c.state == Connection.UP for c in present)
|
||||
overdue = sum(c.state == Connection.OVERDUE for c in present)
|
||||
if up and overdue:
|
||||
return "simplex"
|
||||
if up:
|
||||
return "up"
|
||||
if overdue:
|
||||
return "overdue"
|
||||
return "unknown"
|
||||
|
||||
def conndata(self, cid, addr, rtt, now):
|
||||
if addr[0:7] == "::ffff:":
|
||||
addr = addr[7:]
|
||||
@@ -499,6 +541,8 @@ class Host:
|
||||
self.monitors = []
|
||||
if not hasattr(self, "plugin_timers"):
|
||||
self.plugin_timers = {}
|
||||
if not hasattr(self, "presence"):
|
||||
self.presence = {}
|
||||
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user