diff --git a/.hb.yaml b/.hb.yaml
index 4324a67..02591e2 100644
--- a/.hb.yaml
+++ b/.hb.yaml
@@ -52,7 +52,7 @@ journal_max_backups: 10 # Number of backups to keep
thresholds:
cpu_monitor:
cpu_percent:
- warning: 1.0
+ warning: 80.0
critical: 90.0
memory_monitor:
percent:
@@ -62,19 +62,31 @@ thresholds:
partitions:
/:
percent:
- warning: 8.0
+ warning: 85.0
critical: 90.0
nagios_runner:
+ overall_status_code:
+ warning: 1
+ critical: 2
+ operator: ">="
load_status:
warning: WARNING
- operator: = "="
critical: CRITICAL
- operator: = "="
+ operator: "=="
+ UPS_load:
+ warning: 70
+ critical: 80
+ operator: ">="
UPS_status_code:
warning: 1
critical: 2
operator: ">="
+ nextcloud_apps_status:
+ display: "{nextcloud_apps_output}"
+ warning: 1
+ critical: 2
+ operator: ">="
rtt:
y:
- warning: 0.1
- critical: 10.0
+ warning: 30
+ critical: 250.0
diff --git a/.hb.yaml.swp b/.hb.yaml.swp
index 63c02b4..6dc3601 100644
Binary files a/.hb.yaml.swp and b/.hb.yaml.swp differ
diff --git a/hbd/client/main.py b/hbd/client/main.py
index 1141ec3..0552f09 100644
--- a/hbd/client/main.py
+++ b/hbd/client/main.py
@@ -105,7 +105,7 @@ class AsyncConnection:
msg["time"] = time.time()
# Encode message
- data = dicttos(msg_id, msg, compress=True)
+ data = dicttos(msg_id, msg)
# Send
self.transport.sendto(data, (self.addr, self.port))
diff --git a/hbd/common/proto.py b/hbd/common/proto.py
index ce85bb6..82b7ee3 100644
--- a/hbd/common/proto.py
+++ b/hbd/common/proto.py
@@ -62,7 +62,7 @@ def decode_value(val: str) -> Any:
return val
-def dicttos(ID: str, d: Dict[str, Any], compress: bool = False):
+def dicttos(ID: str, d: Dict[str, Any]):
"""Serialize a dict to protocol message bytes.
If compress is True, the payload is zlib-compressed and the message is
@@ -75,12 +75,9 @@ def dicttos(ID: str, d: Dict[str, Any], compress: bool = False):
encoded_val = encode_value(v)
s.append(f"{k}={encoded_val}")
pk = ";".join(s)
- if compress:
- zpk = zlib.compress(pk.encode(), 6)
- hdr = ("!" + ID + ":").encode()
- return hdr + zpk
- else:
- return (ID + ":" + pk).encode()
+ zpk = zlib.compress(pk.encode(), 6)
+ hdr = ("!" + ID + ":").encode()
+ return hdr + zpk
def stodict(msg: bytes):
@@ -131,7 +128,7 @@ def oldmtodict(msg: bytes):
return stodict(b"HTB:" + msg)
-def encode_plugin_data(plugin_name: str, data: Dict[str, Any], compress: bool = False) -> bytes:
+def encode_plugin_data(plugin_name: str, data: Dict[str, Any]) -> bytes:
"""Encode plugin data into a PLG message.
Args:
@@ -144,7 +141,7 @@ def encode_plugin_data(plugin_name: str, data: Dict[str, Any], compress: bool =
"""
# Add plugin name to data
full_data = {"plugin": plugin_name, **data}
- return dicttos("PLG", full_data, compress)
+ return dicttos("PLG", full_data)
def decode_plugin_data(msg: bytes) -> Dict[str, Any]:
diff --git a/hbd/server/hbdclass.py b/hbd/server/hbdclass.py
index ffb7a97..92ebc83 100644
--- a/hbd/server/hbdclass.py
+++ b/hbd/server/hbdclass.py
@@ -175,7 +175,8 @@ class Connection:
def newaddr(self, addr, rtt, now):
self.lastbeat = now
- self.rtts.append(rtt)
+ if rtt is not None:
+ self.rtts.append(rtt)
if len(self.rtts) > MAXRTTS:
del self.rtts[0]
@@ -292,7 +293,6 @@ class Host:
self.cmds = []
self.cver = 0
self.connections = {}
- self.hdwcounts = [[0, 0], [0, 0], [0, 0]]
# Plugin data storage: {plugin_name: [(timestamp, data), ...]}
self.plugin_data = {}
self.plugin_retention = 100 # Keep last N samples per plugin
@@ -473,42 +473,6 @@ class Host:
"""
return self.plugin_data
- # def dispstate(self):
- # if self.state in ["down", "overdue"]:
- # state = "%s" % self.state
- # elif self.state in ["up", "UP"]:
- # state = ""
- # for x in list(self.connections.keys()):
- # try:
- # state += " %5.1f" % (self.connections[x].rtts[-1])
- # except:
- # state += " %5s" % (self.connections[x].rtts[-1])
- # elif self.state in ["unknown", "UNKNOWN"]:
- # state = ""
- # else:
- # state = "%s" % self.state
- # return state
-
- def dispstats(self):
- if self.doesack != -1:
- if self.upcount > 0:
- r = ""
- for v in range(3):
- a, u = self.hdwcounts[v]
- if (self.upcount - u) != 0:
- vs = "%0.0f" % (
- 100.0 - (((self.doesack - a) * 100.0) / (self.upcount - u))
- )
- if vs == "0":
- vs = ""
- else:
- vs = "-"
- r += '
';
for (const [key, value] of Object.entries(data)) {
@@ -478,6 +540,57 @@
return html;
}
+ function getCurrentPluginName() {
+ // Get currently active plugin name from the active plugin content div
+ const activeContent = document.querySelector('.plugin-content.active');
+ if (activeContent) {
+ return activeContent.dataset.plugin;
+ }
+ return '';
+ }
+
+ function isSimpleKeyValueData(data) {
+ // Check if data is simple key-value pairs (no complex nesting)
+ for (const [key, value] of Object.entries(data)) {
+ if (typeof value === 'object' && value !== null) {
+ // Has nested objects - not simple
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function renderSimpleDataTable(data, timestamp) {
+ let html = '
';
+
+ // Table header
+ html += '';
+ html += '| Name | ';
+ html += 'Value | ';
+ html += '
';
+
+ // Table body
+ html += '';
+ for (const [key, value] of Object.entries(data)) {
+ const label = formatLabel(key);
+ const formattedValue = formatValue(key, value);
+ const unit = getUnit(key);
+
+ html += '';
+ html += `| ${label} | `;
+ html += `${formattedValue}${unit ? ' ' + unit : ''} | `;
+ html += '
';
+ }
+ html += '';
+
+ html += '
';
+
+ const date = new Date(timestamp * 1000);
+ html += `
Last updated: ${date.toLocaleString()}
`;
+
+ return html;
+ }
+
function isInterfaceData(key, value) {
// Check if this is interface/network stats data (I/O counters)
if (key.toLowerCase().includes('interface') && !key.toLowerCase().includes('interface_stats')) {
diff --git a/hbd/server/threshold.py b/hbd/server/threshold.py
index 0a4b891..f89881a 100644
--- a/hbd/server/threshold.py
+++ b/hbd/server/threshold.py
@@ -53,14 +53,24 @@ class AlertState:
self.last_check = time.time()
self.notification_count = 0
self.last_notification = None
+ self.threshold_value = None # The threshold value that triggered alert
+ self.operator = None # The comparison operator (>, <, >=, etc.)
- def update(self, level: AlertLevel, value: Any) -> bool:
+ def update(
+ self,
+ level: AlertLevel,
+ value: Any,
+ threshold_value: Optional[float] = None,
+ operator: Optional[str] = None
+ ) -> bool:
"""
Update alert state.
Args:
level: New alert level
value: Current metric value
+ threshold_value: The threshold value that was exceeded (if applicable)
+ operator: The comparison operator (>, <, >=, etc.)
Returns:
True if state changed (notification needed), False otherwise
@@ -69,6 +79,15 @@ class AlertState:
self.last_check = now
self.last_value = value
+ # Update threshold info when alert is active
+ if level != AlertLevel.OK:
+ self.threshold_value = threshold_value
+ self.operator = operator
+ else:
+ # Clear threshold info when returning to OK
+ self.threshold_value = None
+ self.operator = None
+
# Check if state changed
if level != self.level:
logger.info(
@@ -87,7 +106,7 @@ class AlertState:
def to_dict(self) -> dict:
"""Convert alert state to dictionary for serialization."""
- return {
+ result = {
"metric_path": self.metric_path,
"level": self.level.name,
"since": self.since,
@@ -95,6 +114,14 @@ class AlertState:
"last_check": self.last_check,
"notification_count": self.notification_count,
}
+
+ # Include threshold info if available
+ if self.threshold_value is not None:
+ result["threshold_value"] = self.threshold_value
+ if self.operator is not None:
+ result["operator"] = self.operator
+
+ return result
def __str__(self):
return self.to_dict().__str__()
@@ -107,6 +134,7 @@ class ThresholdConfig:
metric_path: str,
warning: Optional[float] = None,
critical: Optional[float] = None,
+ display: Optional[str] = None,
operator: str = ">",
hysteresis: float = 0.0,
enabled: bool = True,
@@ -127,6 +155,7 @@ class ThresholdConfig:
self.critical = critical
self.enabled = enabled
self.hysteresis = hysteresis
+ self.display = display
# Parse operator
try:
@@ -302,6 +331,7 @@ class ThresholdChecker:
warning = threshold_config.get("warning")
critical = threshold_config.get("critical")
operator = threshold_config.get("operator", ">")
+ display = threshold_config.get("display")
hysteresis = threshold_config.get("hysteresis", 0.1) # 10% default
enabled = threshold_config.get("enabled", True)
@@ -316,6 +346,7 @@ class ThresholdChecker:
operator=operator,
hysteresis=hysteresis,
enabled=enabled,
+ display=display
)
self.thresholds[metric_path] = threshold
@@ -345,7 +376,7 @@ class ThresholdChecker:
operator = threshold_config.get("operator", ">")
hysteresis = threshold_config.get("hysteresis", 0.1)
enabled = threshold_config.get("enabled", True)
-
+ display = threshold_config.get("display")
if warning is None and critical is None:
continue
@@ -356,6 +387,7 @@ class ThresholdChecker:
operator=operator,
hysteresis=hysteresis,
enabled=enabled,
+ display=display
)
self.thresholds[metric_path] = threshold
@@ -382,6 +414,7 @@ class ThresholdChecker:
operator = threshold_config.get("operator", ">")
hysteresis = threshold_config.get("hysteresis", 0.1) # 10% default
enabled = threshold_config.get("enabled", True)
+ display = threshold_config.get("display")
if warning is None and critical is None:
logger.warning("No RTT thresholds defined for %s, skipping", hostname)
@@ -394,6 +427,7 @@ class ThresholdChecker:
operator=operator,
hysteresis=hysteresis,
enabled=enabled,
+ display=display
)
self.thresholds[metric_path] = threshold
@@ -440,14 +474,21 @@ class ThresholdChecker:
alert_state.level
)
+ # Determine which threshold was exceeded
+ threshold_value = None
+ if new_level == AlertLevel.CRITICAL and threshold.critical is not None:
+ threshold_value = threshold.critical
+ elif new_level == AlertLevel.WARNING and threshold.warning is not None:
+ threshold_value = threshold.warning
+
# Update state and check for changes
old_level = alert_state.level
- if alert_state.update(new_level, value):
- self._trigger_notification(host_name, metric_path, old_level, new_level, value)
+ if alert_state.update(new_level, value, threshold_value, threshold.operator.value):
+ self._trigger_notification(host_name, metric_path, old_level, new_level, value, threshold)
return (old_level, new_level)
elif new_level != AlertLevel.OK:
# Check if we should re-notify
- self._check_renotify(host_name, alert_state, metric_path, value)
+ self._check_renotify(host_name, alert_state, metric_path, value, threshold)
return None
@@ -493,14 +534,21 @@ class ThresholdChecker:
alert_state.level
)
+ # Determine which threshold was exceeded
+ threshold_value = None
+ if new_level == AlertLevel.CRITICAL and threshold.critical is not None:
+ threshold_value = threshold.critical
+ elif new_level == AlertLevel.WARNING and threshold.warning is not None:
+ threshold_value = threshold.warning
+
# Update state and check for changes
old_level = alert_state.level
- if alert_state.update(new_level, value):
+ if alert_state.update(new_level, value, threshold_value, threshold.operator.value):
state_changes.append((metric_path, old_level, new_level, value))
- self._trigger_notification(host_name, metric_path, old_level, new_level, value)
+ self._trigger_notification(host_name, metric_path, old_level, new_level, value, threshold)
elif new_level != AlertLevel.OK:
# Check if we should re-notify
- self._check_renotify(host_name, alert_state, metric_path, value)
+ self._check_renotify(host_name, alert_state, metric_path, value, threshold)
# Check nested metrics (e.g., partition data in disk_monitor)
self._check_nested_metrics(
@@ -550,18 +598,26 @@ class ThresholdChecker:
alert_state.level
)
+ # Determine which threshold was exceeded
+ threshold_value = None
+ if new_level == AlertLevel.CRITICAL and threshold.critical is not None:
+ threshold_value = threshold.critical
+ elif new_level == AlertLevel.WARNING and threshold.warning is not None:
+ threshold_value = threshold.warning
+
old_level = alert_state.level
- if alert_state.update(new_level, value):
+ if alert_state.update(new_level, value, threshold_value, threshold.operator.value):
state_changes.append((metric_path, old_level, new_level, value))
self._trigger_notification(
host_name,
metric_path,
old_level,
new_level,
- value
+ value,
+ threshold
)
elif new_level != AlertLevel.OK:
- self._check_renotify(host_name, alert_state, metric_path, value)
+ self._check_renotify(host_name, alert_state, metric_path, value, threshold)
def _trigger_notification(
self,
@@ -570,18 +626,35 @@ class ThresholdChecker:
old_level: AlertLevel,
new_level: AlertLevel,
value: Any,
+ threshold: ThresholdConfig,
):
"""Trigger a notification for an alert state change."""
+ # Determine which threshold was exceeded
+ threshold_value = None
+ if new_level == AlertLevel.CRITICAL and threshold.critical is not None:
+ threshold_value = threshold.critical
+ elif new_level == AlertLevel.WARNING and threshold.warning is not None:
+ threshold_value = threshold.warning
+
+ # Format operator symbol
+ op_symbol = threshold.operator.value
+
# Format message
if new_level == AlertLevel.OK:
lvl = "RECOVERED"
message = f"{metric_path} = {value} ({old_level.name} -> OK)"
elif new_level == AlertLevel.WARNING:
lvl = "WARNING"
- message = f"{metric_path} = {value}"
+ if threshold_value is not None:
+ message = f"{metric_path} = {value} (threshold: {op_symbol} {threshold_value})"
+ else:
+ message = f"{metric_path} = {value}"
elif new_level == AlertLevel.CRITICAL:
lvl = "CRITICAL"
- message = f"{metric_path} = {value}"
+ if threshold_value is not None:
+ message = f"{metric_path} = {value} (threshold: {op_symbol} {threshold_value})"
+ else:
+ message = f"{metric_path} = {value}"
else:
lvl = "UNKNOWN"
message = f"{metric_path} = {value}"
@@ -617,6 +690,7 @@ class ThresholdChecker:
alert_state: AlertState,
metric_path: str,
value: Any,
+ threshold: ThresholdConfig,
):
"""Check if we should send a repeat notification."""
if alert_state.level == AlertLevel.OK:
@@ -632,8 +706,21 @@ class ThresholdChecker:
return
if (now - alert_state.last_notification) >= self.renotify_interval:
+ # Determine which threshold is active
+ threshold_value = None
+ if alert_state.level == AlertLevel.CRITICAL and threshold.critical is not None:
+ threshold_value = threshold.critical
+ elif alert_state.level == AlertLevel.WARNING and threshold.warning is not None:
+ threshold_value = threshold.warning
+
+ # Format operator symbol
+ op_symbol = threshold.operator.value
+
# Time to re-notify
- message = f"REMINDER ({alert_state.level.name}): {host_name} - {metric_path} = {value} (ongoing for {int(now - alert_state.since)}s)"
+ if threshold_value is not None:
+ message = f"REMINDER ({alert_state.level.name}): {host_name} - {metric_path} = {value} (threshold: {op_symbol} {threshold_value}, ongoing for {int(now - alert_state.since)}s)"
+ else:
+ message = f"REMINDER ({alert_state.level.name}): {host_name} - {metric_path} = {value} (ongoing for {int(now - alert_state.since)}s)"
if self.notification_callback:
try:
diff --git a/hbd/server/udp.py b/hbd/server/udp.py
index 1709e16..84b59a2 100644
--- a/hbd/server/udp.py
+++ b/hbd/server/udp.py
@@ -47,7 +47,7 @@ def parse_message(data: bytes):
return msg
-def dicttos(ID, d, compress=False):
+def dicttos(ID, d):
s = []
for k in d:
if isinstance(d[k], float):
@@ -55,13 +55,9 @@ def dicttos(ID, d, compress=False):
else:
s.append("%s=%s" % (k, d[k]))
pk = ";".join(s)
- if compress:
- zpk = zlib.compress(pk.encode(), 6)
- ID = "!" + ID + ":"
- opk = ID.encode() + zpk
- else:
- zpk = pk
- opk = ID + ":" + zpk
+ zpk = zlib.compress(pk.encode(), 6)
+ ID = "!" + ID + ":"
+ opk = ID.encode() + zpk
return opk
@@ -119,12 +115,24 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
cid = msg.get("id", 0)
try:
- rtt = float(msg.get("rtt", None))
- except Exception:
+ rtt = float(msg.get("rtt"))
+ except TypeError:
rtt = None
if msg.get("ID") == "HTB":
host.doesack = msg.get("acks", -1)
+ # send ACK back
+ rmsg = {"time": __import__("time").time()}
+ if host.cver < 1:
+ opkt = b"ACK"
+ else:
+ opkt = dicttos("ACK", rmsg)
+ try:
+ transport.sendto(opkt, addr)
+ except Exception as e:
+ if DEBUG > 0:
+ print(("cannot send ack: %s" % e))
+
elif msg.get("ID") == "PLG":
# Handle plugin data message
plugin_name = msg.get("plugin")
@@ -277,18 +285,6 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
alert_states=host.alert_states
)
- # send ACK back
- rmsg = {"time": __import__("time").time()}
- if host.cver < 1:
- opkt = b"ACK"
- else:
- opkt = dicttos("ACK", rmsg, host.cver > 1)
- try:
- transport.sendto(opkt, addr)
- except Exception as e:
- if DEBUG > 0:
- print(("cannot send ack: %s" % e))
-
# send any commands we have queued
while len(host.cmds):
op, rmsg = host.cmds[0]
@@ -311,7 +307,7 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
if isinstance(opkt, str):
opkt = opkt.encode()
else:
- opkt = dicttos(op, rmsg, True)
+ opkt = dicttos(op, rmsg)
try:
transport.sendto(opkt, addr)
except Exception as e:
diff --git a/hbdclass.py- b/hbdclass.py-
deleted file mode 100644
index ed7e16d..0000000
--- a/hbdclass.py-
+++ /dev/null
@@ -1,380 +0,0 @@
-"""
-host and connection class shared between hbd and
-the websit's heartbeat.py
-
-"""
-
-import time
-import json
-import copy
-import queue
-
-num = 0
-
-MAXRTTS = 10
-
-DEBUG = 2
-
-
-def log(host, m):
- if DEBUG:
- print("class log: %s %s" % (host, m))
-
-
-class Connection:
- # map of addrs to names
-
- htab = {}
- UNKNOWN = "unknown"
- UP = "up"
- DOWN = "down"
- OVERDUE = "overdue"
-
- def __init__(self, host, cid, addr, afam):
- self.host = host
- self.cid = cid
- if addr[0:7] == "::ffff:":
- addr = addr[7:]
- self.addr = addr
- self.afam = afam
- self.rtts = [0]
- self.lastbeat = time.time()
- self.statetime = self.lastbeat
- self.deltastatetime = "computed"
- self.state = Connection.UNKNOWN
-
- if host:
- Connection.htab[addr] = self.host.name
- if self.host.isDynDns():
- log(self.host.name, "dns update %s" % self.addr)
- Host.dnsQ.put((self.host.name, self.addr))
-
- def registerDns(self):
- Host.dnsQ.put((self.host.name, self.addr))
-
- def clearstate(self):
- d = {}
- d["addr"] = ""
- d["rtt"] = ""
- d["lastbeat"] = ""
- d["state"] = ""
- d["statetime"] = ""
- d["deltastatetime"] = ""
- d["rttstate"] = ""
- return d
-
- def statedict(self, Null=False):
- d = self.clearstate()
- now = time.time()
- if not Null:
- d["addr"] = self.addr
- if self.rtts[-1]:
- d["rtt"] = "%0.1f" % self.rtts[-1]
- elif self.state == Connection.UNKNOWN:
- d["rtt"] = ""
- else:
- d["rtt"] = "?"
- d["lastbeat"] = self.lastbeat
- if self.state == Connection.OVERDUE:
- d["state"] = "
%s" % self.state
- else:
- d["state"] = self.state
- if self.state == Connection.UP:
- d["rttstate"] = d["rtt"]
- elif self.state == Connection.OVERDUE:
- d["rttstate"] = ""
- else:
- d["rttstate"] = d["state"]
- d["statetime"] = time.strftime(
- "%Y-%m-%d %H:%M:%S", time.localtime(self.statetime)
- )
- delta = now - self.statetime
-
- if self.state == Connection.UNKNOWN:
- d["deltastatetime"] = ""
- elif delta > 86400:
- # d['deltastatetime'] = time.strftime("%d %H:%M:%S", time.gmtime(delta))
- d["deltastatetime"] = "%0.1f days" % (delta / 86400.0)
- elif delta > 3600:
- # d['deltastatetime'] = time.strftime("%H:%M:%S", time.gmtime(delta))
- d["deltastatetime"] = time.strftime("%k:%M hrs", time.gmtime(delta))
- # d['deltastatetime'] = "%0.1f hrs" % (delta / 3600.)
- elif delta > 60:
- # d['deltastatetime'] = time.strftime("%M:%S", time.gmtime(delta))
- d["deltastatetime"] = time.strftime("%M:%S mins", time.gmtime(delta))
- # d['deltastatetime'] = "%0.1f mins" % (delta / 60.)
- else:
- # d['deltastatetime'] = time.strftime("%S", time.gmtime(delta))
- d["deltastatetime"] = "%i secs" % (delta)
- if self.state == Connection.UNKNOWN and now - self.lastbeat > 86400 * 10:
- d = self.clearstate()
-
- return d
-
- def headerdict(self, afam):
- d = {}
- d["addr"] = "%s Addr" % afam
- d["rtt"] = "Latencey"
- d["lastbeat"] = "Last Contact"
- d["state"] = "State"
- d["statetime"] = "Last State"
- d["rttstate"] = "Reach"
- d["deltastatetime"] = "Last State"
- return d
-
- def jsons(self):
- return json.dumps(self.__dict__)
-
- # set new state, return number of secs in previous state
- def newstate(self, state, now, when=0):
- self.state = state
- delta = now - when
- s = delta - self.statetime
- self.statetime = delta
- return s
-
- def getstate(self):
- return self.state
-
- def newaddr(self, addr, rtt, now):
- self.lastbeat = now
- self.rtts.append(rtt)
- if len(self.rtts) > MAXRTTS:
- del self.rtts[0]
-
- if self.addr == addr:
- r = None
- else:
- r = "changed from %s to %s" % (self.addr, addr)
- try:
- del Connection.htab[self.addr]
- except:
- pass
- self.addr = addr
- Connection.htab[addr] = self.host.name
- if self.host.isDynDns():
- Host.dnsQ.put((self.host.name, self.addr))
- return r
-
-
-#
-class Host:
- # Table of Hosts
- hosts = {}
- dnsQ = queue.Queue()
-
- def __init__(self, name):
- global num
- self.name = name
- if name:
- num += 1
- Host.hosts[name] = self
- self.num = num
- self.dyn = False
- self.watched = False
- self.upcount = 0
- self.interval = 0
- self.doesack = -1
- self.cmds = []
- self.cver = 0
- self.connections = {}
- self.hdwcounts = [[0, 0], [0, 0], [0, 0]]
-
- def statedict(self):
- d = {}
- d["name"] = self.name
- if self.dyn:
- d["name"] += "*"
- if self.watched:
- d["name"] = "
%s" % d["name"]
- d["dyn"] = str(self.dyn)
- d["ver"] = str(self.cver)
- d["num"] = self.num
- for c in ["IPv4", "IPv6"]:
- if c in self.connections:
- cs = self.connections[c].statedict()
- else:
- cs = ubConnection.statedict(True)
- for csv in cs:
- d["%s.%s" % (c, csv)] = cs[csv]
-
- return d
-
- def headerdict(self):
- d = {}
- d["name"] = "Name"
- d["dyn"] = "Dyn"
- d["ver"] = "Ver"
- d["num"] = "??"
- for c in ["IPv4", "IPv6"]:
- cs = ubConnection.headerdict(c)
- for csv in cs:
- d["%s.%s" % (c, csv)] = cs[csv]
- return d
-
- def registerDns(self):
- for af in self.connections:
- self.connections[af].registerDns()
-
- def stateinfo(self):
- ddict = {}
- for d in self.__dict__:
- if d == "connections":
- cl = []
- for c in self.connections:
- # dirty ugly hack: fix conn to host backpointer
- cld = copy.deepcopy(self.connections[c].__dict__)
- cld["host"] = cld["host"].name
- cl.append(cld)
- ddict[d] = cl
- else:
- ddict[d] = self.__dict__[d]
- return ddict
-
- def jsons(self):
- return json.dumps(self.stateinfo())
-
- def setcver(self, cver):
- self.cver = cver
-
- def isDynDns(self):
- return self.dyn
-
- def isIPv4(self, addr):
- if isinstance(addr, tuple):
- return addr[0].find(".") > 0
- else:
- return addr.find(".") > 0
-
- def conndata(self, cid, addr, rtt, now):
- if addr[0:7] == "::ffff:":
- addr = addr[7:]
- if self.isIPv4(addr):
- afam = "IPv4"
- else:
- afam = "IPv6"
-
- if afam not in self.connections:
- self.connections[afam] = Connection(self, cid, addr, afam)
-
- conn = self.connections[afam]
- res = conn.newaddr(addr, rtt, now)
- return conn, res
-
- # called when reloading class from pickle, add new fields here
- def fixup(self):
- for c in ["IPv4", "IPv6"]:
- if c in self.connections:
- addr = self.connections[c].addr
- if addr[0:7] == "::ffff:":
- addr = addr[7:]
- self.connections[c].addr = addr
-
- pass
-
- # def dispstate(self):
- # if self.state in ["down", "overdue"]:
- # state = "
%s" % self.state
- # elif self.state in ["up", "UP"]:
- # state = ""
- # for x in list(self.connections.keys()):
- # try:
- # state += " %5.1f" % (self.connections[x].rtts[-1])
- # except:
- # state += " %5s" % (self.connections[x].rtts[-1])
- # elif self.state in ["unknown", "UNKNOWN"]:
- # state = ""
- # else:
- # state = "%s" % self.state
- # return state
-
- def dispstats(self):
- if self.doesack != -1:
- if self.upcount > 0:
- # return "(%0.1f%%) %s %s %s " % ((self.doesack * 100.0) / self.upcount, self.doesack, self.upcount, self.hdwcounts)
- r = ""
- for v in range(3):
- a, u = self.hdwcounts[v]
- if (self.upcount - u) != 0:
- vs = "%0.0f" % (
- 100.0 - (((self.doesack - a) * 100.0) / (self.upcount - u))
- )
- if vs == "0":
- vs = ""
- else:
- vs = "-"
- r += '
%s | ' % vs
- return r
- else:
- return "
(%s) | | | " % (self.doesack)
- return '
N/A | | >'
-
- hostfields_long = [
- "name",
- "IPv4.addr",
- "IPv4.state",
- ("IPv4.rtt", 'style="text-align: right;"'),
- ("IPv4.statetime", 'style="text-align: right;"'),
- "IPv6.addr",
- "IPv6.state",
- ("IPv6.rtt", 'style="text-align: right;"'),
- ("IPv6.statetime", 'style="text-align: right;"'),
- "ver",
- ]
-
- hostfields_short = [
- "name",
- ("IPv4.rttstate", 'style="text-align: right;"'),
- ("IPv4.deltastatetime", 'style="text-align: right;"'),
- ("IPv6.rttstate", 'style="text-align: right;"'),
- ("IPv6.deltastatetime", 'style="text-align: right;"'),
- ]
-
- def gene(self, tag, v, attrib=None):
- if attrib:
- a = " %s" % attrib
- else:
- a = ""
- return "<%s%s>%s%s>" % (tag, a, v, tag)
-
- def htmltable(self, tag, hd, short):
- if short:
- hostfields = Host.hostfields_short
- else:
- hostfields = Host.hostfields_long
- h = []
- for f in hostfields:
- if isinstance(f, tuple):
- h.append(self.gene(tag, hd[f[0]], f[1]))
- else:
- h.append(self.gene(tag, hd[f]))
- return self.gene("tr", "\n".join(h))
-
- def buildhosttable(self, short=False):
- if DEBUG > 1:
- print("DBG buildhosttable: start")
- res = []
- res.append('
')
- res.append(ubHost.htmltable("th", ubHost.headerdict(), short))
- hosts_sorted = list(Host.hosts.keys())
- if len(hosts_sorted):
- hosts_sorted.sort()
- for h in hosts_sorted:
- res.append(ubHost.htmltable("td", Host.hosts[h].statedict(), short))
- res.append("
")
- if DEBUG > 1:
- print("DBG buildhosttable: %s" % res)
- return res
-
- def buildmsgtable(self, msgs):
- res = []
- le = max(40 - len(Host.hosts), 3)
- res.append("
Log of Events
")
- for m in msgs[len(msgs) - le:]:
- res.append("%s
" % m)
- return res
-
-
-# create fake "unbound objects", remove in Python 3.0
-ubHost = Host(None)
-ubConnection = Connection(None, "", "", "")
diff --git a/test_plugins.py b/test_plugins.py
index ce59a28..3c9c751 100644
--- a/test_plugins.py
+++ b/test_plugins.py
@@ -93,7 +93,7 @@ async def test_plugins():
print(f" Original data: {test_data}")
# Encode
- encoded = dicttos("PLG", test_data, compress=True)
+ encoded = dicttos("PLG", test_data)
print(f" Encoded ({len(encoded)} bytes): {encoded[:50]}...")
# Decode
diff --git a/tests/test_udp.py b/tests/test_udp.py
index 23a887c..8e05fa2 100644
--- a/tests/test_udp.py
+++ b/tests/test_udp.py
@@ -9,6 +9,6 @@ def test_parse_message_uncompressed():
def test_parse_message_compressed():
- raw = dicttos("ACK", {"time": 1}, compress=True)
+ raw = dicttos("ACK", {"time": 1})
m = parse_message(raw)
assert "ID" in m