diff --git a/REFACTORING.md b/REFACTORING.md
deleted file mode 100644
index afd0fa0..0000000
--- a/REFACTORING.md
+++ /dev/null
@@ -1,234 +0,0 @@
-# HBD/HBC Separation Refactoring
-
-## Overview
-
-The heartbeat monitoring system has been refactored into a modular package structure with separate client and server components. This allows users to install only what they need and provides clear separation of concerns.
-
-## New Package Structure
-
-```
-hbd/
-├── __init__.py # Main package (minimal)
-├── client/ # HBC - System monitoring client
-│ ├── __init__.py
-│ ├── main.py # Entry point (was hbc.py)
-│ ├── config.py # Client-specific configuration
-│ ├── plugin.py # Plugin framework
-│ ├── threshold.py # Threshold checking
-│ └── plugins/ # Monitoring plugins
-│ ├── cpu_monitor.py
-│ ├── disk_monitor.py
-│ ├── memory_monitor.py
-│ ├── network_monitor.py
-│ ├── filesystem_info.py
-│ ├── os_info.py
-│ └── nagios_runner.py
-├── server/ # HBD - Heartbeat daemon/server
-│ ├── __init__.py
-│ ├── main.py # Server runtime (was server.py)
-│ ├── cli.py # Command-line interface
-│ ├── config.py # Server-specific configuration
-│ ├── http.py # HTTP/REST API
-│ ├── ws.py # WebSocket server
-│ ├── udp.py # UDP heartbeat listener
-│ ├── dns.py # DNS update functionality
-│ ├── notify.py # Notification handlers
-│ ├── monitor.py # Host monitoring
-│ ├── hbdclass.py # Host class definitions
-│ ├── journal.py # Message journaling
-│ ├── templates/ # Jinja2 web templates
-│ └── static/ # Web UI assets
-└── common/ # Shared utilities
- ├── __init__.py
- ├── proto.py # Protocol encoding/decoding
- └── utils.py # Common utilities
-
-## Configuration Files
-
-### Client Configuration (hbd/client/config.py)
-
-Client-specific defaults:
-- `hb_port`: Port where hbd servers listen (default: 50003)
-- `interval`: Heartbeat interval in seconds (default: 10)
-- `plugins`: Per-plugin configuration
-- `thresholds`: Threshold configuration for monitoring
-
-### Server Configuration (hbd/server/config.py)
-
-Server-specific defaults:
-- `hb_port`: Port to listen for heartbeats (default: 50003)
-- `hbd_port`: HTTP API port (default: 50004)
-- `ws_port`: WebSocket port (default: 50005)
-- `logfile`: Log file path
-- `pushsrv`, `pushover_token`, etc.: Notification settings
-- `watchhosts`, `dyndnshosts`: Host monitoring
-- `smtpserver`, etc.: Email settings
-- `journal_*`: Message journaling settings
-
-## Installation Options
-
-### Install Core Only (minimal, PyYAML only)
-```bash
-pip install hbd
-```
-
-### Install Client Only (for monitoring)
-```bash
-pip install hbd[client]
-# Installs: PyYAML, psutil
-```
-
-### Install Server Only (for daemon)
-```bash
-pip install hbd[server]
-# Installs: PyYAML, websockets, mattermostdriver, aiohttp, Jinja2
-```
-
-### Install Everything
-```bash
-pip install hbd[all]
-# Installs all dependencies for both client and server
-```
-
-### Development Installation
-```bash
-pip install -e ".[dev]"
-# Includes all dependencies plus testing/linting tools
-```
-
-## Command-Line Interfaces
-
-### HBC (Client)
-```bash
-hbc [options] host1 [host2 ...]
-
-# Entry point: hbd.client.main:main
-# Location: hbd/client/main.py
-```
-
-### HBD (Server)
-```bash
-hbd [options]
-
-# Entry point: hbd.server.cli:main
-# Location: hbd/server/cli.py → hbd/server/main.py
-```
-
-## Import Changes
-
-### Client Code
-```python
-# Old imports
-from .config import load_config
-from .proto import dicttos, stodict
-from .plugin import PluginRegistry
-
-# New imports
-from .config import load_config # Still in client/
-from ..common.proto import dicttos # Moved to common/
-from .plugin import PluginRegistry # Still in client/
-```
-
-### Server Code
-```python
-# Old imports
-from .config import load_config
-from .proto import stodict
-from .threshold import AlertLevel
-
-# New imports
-from .config import load_config # Server-specific config
-from ..common.proto import stodict # Moved to common/
-from ..client.threshold import AlertLevel # Client module
-```
-
-### Plugin Code
-```python
-# Old import
-from hbd.plugin import MonitorPlugin
-
-# New import
-from hbd.client.plugin import MonitorPlugin
-```
-
-## Benefits
-
-1. **Modular Installation**: Install only what you need
- - Client-only systems don't need web server dependencies
- - Server-only systems don't need psutil
-
-2. **Clearer Architecture**: Explicit separation of concerns
- - Client: System monitoring and data collection
- - Server: Heartbeat reception, web UI, notifications
- - Common: Shared protocol and utilities
-
-3. **Independent Evolution**: Client and server can evolve separately
- - Different release cycles possible
- - Clear API boundaries via common/
-
-4. **Smaller Footprint**: Reduced dependency installation
- - Client: ~1 dependency (psutil)
- - Server: ~4 dependencies (websockets, aiohttp, Jinja2, mattermostdriver)
-
-## Migration Guide
-
-### For Existing Installations
-
-1. **Reinstall the package**:
- ```bash
- pip install -e ".[all]" # For development
- # or
- pip install hbd[all] # For production
- ```
-
-2. **Configuration files remain unchanged**:
- - Both client and server read from `~/.hb.yaml`
- - All existing config keys are supported in both configs
- - Server has additional keys (journal, websocket, email, etc.)
- - Client has minimal keys (interval, plugins, thresholds)
-
-3. **Commands remain the same**:
- - `hbc` command works identically
- - `hbd` command works identically
-
-### For New Deployments
-
-1. **Client-only system** (monitoring host):
- ```bash
- pip install hbd[client]
- hbc server1.example.com server2.example.com
- ```
-
-2. **Server-only system** (monitoring daemon):
- ```bash
- pip install hbd[server]
- hbd -c /etc/hbd.yaml -f
- ```
-
-3. **Combined system** (dev/test):
- ```bash
- pip install hbd[all]
- ```
-
-## Testing
-
-All imports and entry points have been tested and validated:
-- ✅ Package imports work correctly
-- ✅ `hbc` command entry point functional
-- ✅ `hbd` command entry point functional
-- ✅ Optional dependencies properly configured
-- ✅ All internal imports updated
-
-## Files Archived
-
-The following files were renamed to avoid conflicts:
-- `hbd/config.py` → `hbd/config.py.old` (split into client/server configs)
-- `hbd/hbc_old.py` → `hbd/hbc_old.py.bak` (backup file)
-
-## Next Steps
-
-1. Test client functionality with a monitoring host
-2. Test server functionality with web UI and notifications
-3. Update documentation (README.md) with new structure
-4. Consider publishing to PyPI with new structure
-5. Update any deployment scripts/Dockerfiles to use optional dependencies
diff --git a/hbd/Plan.md b/hbd/Plan.md
deleted file mode 100644
index e1f78fa..0000000
--- a/hbd/Plan.md
+++ /dev/null
@@ -1,21 +0,0 @@
-Plan the following changes, ask questions to clarify before implementing
-
-Re-factor the notification system:
-- use available libraries for pushover, matrix, email and sms notifications.
-- notifications have a title/subject: alert_type (recover/warning/critical), a body (info from threshold check) and a link to the host plugin metrix page
-- define a list of notification channels for each user
-- notifications are dispatched to users that are listed as managers for the host
-
-
-
-1 - correct
-2 - for now channels are defined globaly
-3 - matrix-nio)sounds good, homeserver URL, access token, room ID per channel?
-4 - use the REST api provided by https://voip.ms/api/v1/rest.php
-5 - The page does not exist yet, point at the host tab in the /plugins
-6 - per-channel minimum severity is a good idea, go fo it
-7 - yes
-
-1 - use base_url, there might not have been any incoming requests yet
-2 - use same asyncio loop for matrix-nio
-3 - for now, just silently do nothing
\ No newline at end of file
diff --git a/hbd/server/http.py b/hbd/server/http.py
index 9c65fb3..ab4212b 100644
--- a/hbd/server/http.py
+++ b/hbd/server/http.py
@@ -520,8 +520,8 @@ async def start(
tmpl = env.get_template("plugins.html")
body = tmpl.render(
- title="Plugin Metrics - Heartbeat",
- header="Plugin Metrics",
+ title="Host Overview - Heartbeat",
+ header="Host Overview",
hosts=hosts_with_plugins,
current_user=current_user.to_dict() if current_user else None,
active_page="plugins",
diff --git a/hbd/server/templates/nav.html b/hbd/server/templates/nav.html
index edc4170..70467cb 100644
--- a/hbd/server/templates/nav.html
+++ b/hbd/server/templates/nav.html
@@ -4,7 +4,7 @@
Live Dashboard
-
Plugin Metrics
+
Host Overview
Alerts
{% if current_user and current_user.admin %}
Settings
diff --git a/hbd/server/templates/plugins.html b/hbd/server/templates/plugins.html
index d2d150a..94809b8 100644
--- a/hbd/server/templates/plugins.html
+++ b/hbd/server/templates/plugins.html
@@ -25,306 +25,322 @@
font-size: 0.9em;
}
+ /* ── Host cards ─────────────────────────────────────────────── */
+
.host-card {
background: white;
border-radius: 6px;
- padding: 10px 15px;
+ padding: 0;
margin-bottom: 10px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
- transition: all 0.2s;
- }
-
- .host-card.collapsed .host-body {
- display: none;
}
.host-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- cursor: pointer;
- user-select: none;
- padding: 5px 0;
- }
-
- .host-header:hover {
- background: #f9f9f9;
- }
-
- .host-title {
display: flex;
align-items: center;
gap: 10px;
+ cursor: pointer;
+ user-select: none;
+ padding: 10px 15px;
+ border-radius: 6px;
+ }
+
+ .host-header:hover { background: #f9f9f9; border-radius: 6px 6px 0 0; }
+ .host-card.collapsed .host-header:hover { border-radius: 6px; }
+
+ .host-left {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-shrink: 0;
}
.collapse-icon {
- font-size: 1.2em;
- color: #666;
+ font-size: 1em;
+ color: #888;
transition: transform 0.2s;
- min-width: 20px;
+ min-width: 16px;
}
- .host-card.collapsed .collapse-icon {
- transform: rotate(-90deg);
- }
+ .host-card.collapsed .collapse-icon { transform: rotate(-90deg); }
.host-name {
- font-size: 1.1em;
+ font-size: 1.05em;
font-weight: bold;
color: #333;
+ white-space: nowrap;
}
+ /* ── Glance strip ───────────────────────────────────────────── */
+
+ .glance-strip {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex: 1;
+ flex-wrap: wrap;
+ padding: 0 12px;
+ }
+
+ .glance-chip {
+ font-size: 0.78em;
+ padding: 2px 9px;
+ border-radius: 10px;
+ font-weight: 500;
+ white-space: nowrap;
+ background: #e8f5e9;
+ color: #2e7d32;
+ }
+
+ .glance-chip.warn { background: #fff3e0; color: #e65100; }
+ .glance-chip.crit { background: #ffebee; color: #b71c1c; }
+ .glance-chip.neutral { background: #f5f5f5; color: #555; }
+ .glance-loading { font-size: 0.8em; color: #bbb; font-style: italic; }
+
+ /* ── Host right zone ────────────────────────────────────────── */
+
+ .host-right {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-shrink: 0;
+ }
+
+ .nagios-badge {
+ font-size: 0.75em;
+ font-weight: bold;
+ padding: 2px 10px;
+ border-radius: 10px;
+ background: #9e9e9e;
+ color: white;
+ text-transform: uppercase;
+ white-space: nowrap;
+ }
+
+ .nagios-badge.ok { background: #4caf50; }
+ .nagios-badge.warning { background: #ff9800; }
+ .nagios-badge.critical { background: #f44336; }
+
+ .os-label {
+ font-size: 0.75em;
+ color: #999;
+ white-space: nowrap;
+ max-width: 200px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ /* ── Host body ──────────────────────────────────────────────── */
+
.host-body {
- padding-top: 10px;
+ padding: 8px 15px 12px;
+ border-top: 1px solid #f0f0f0;
}
- .plugin-pills {
+ .host-card.collapsed .host-body { display: none; }
+
+ /* ── Plugin accordions ──────────────────────────────────────── */
+
+ .plugin-accordion {
+ border: 1px solid #e8e8e8;
+ border-radius: 4px;
+ margin-bottom: 5px;
+ overflow: hidden;
+ }
+
+ .plugin-acc-header {
display: flex;
- gap: 6px;
- flex-wrap: wrap;
+ align-items: center;
+ gap: 10px;
+ padding: 7px 12px;
+ cursor: pointer;
+ background: #fafafa;
+ user-select: none;
+ }
+
+ .plugin-acc-header:hover { background: #f0f4ff; }
+
+ .acc-icon {
+ font-size: 0.7em;
+ color: #999;
+ transition: transform 0.15s;
+ min-width: 12px;
+ }
+
+ .plugin-accordion:not(.collapsed) .acc-icon { transform: rotate(90deg); }
+
+ .plugin-label {
+ font-weight: 600;
+ font-size: 0.85em;
+ color: #444;
+ min-width: 140px;
+ }
+
+ .plugin-summary {
+ font-size: 0.82em;
+ color: #888;
+ flex: 1;
+ }
+
+ .plugin-accordion.collapsed .plugin-acc-body { display: none; }
+
+ .plugin-acc-body { padding: 10px 12px; }
+
+ /* ── Tables ─────────────────────────────────────────────────── */
+
+ .data-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.85em;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.08);
+ border-radius: 4px;
+ overflow: hidden;
margin-bottom: 10px;
}
- .plugin-pill {
- padding: 4px 12px;
- background: #e3f2fd;
- border: 1px solid #90caf9;
- border-radius: 15px;
- cursor: pointer;
- transition: all 0.2s;
- font-size: 0.85em;
- }
+ .data-table thead { background: #2196f3; color: white; }
- .plugin-pill:hover {
- background: #90caf9;
- color: white;
- }
-
- .plugin-pill.active {
- background: #2196f3;
- color: white;
- border-color: #2196f3;
- }
-
- .plugin-content {
- margin-top: 10px;
- display: none;
- }
-
- .plugin-content.active {
- display: block;
- }
-
- .metric-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: 10px;
- margin-top: 10px;
- }
-
- .metric-card {
- background: #fafafa;
- border-left: 3px solid #2196f3;
- padding: 8px 12px;
- border-radius: 3px;
- }
-
- .metric-label {
- font-size: 0.75em;
- color: #666;
+ .data-table th {
+ padding: 7px 10px;
+ text-align: left;
+ font-weight: 600;
text-transform: uppercase;
- letter-spacing: 0.3px;
- margin-bottom: 3px;
+ font-size: 0.75em;
+ letter-spacing: 0.4px;
}
- .metric-value {
- font-size: 1.4em;
- font-weight: bold;
+ .data-table th.num { text-align: right; }
+ .data-table th.center { text-align: center; }
+
+ .data-table td {
+ padding: 6px 10px;
+ border-top: 1px solid #e8e8e8;
color: #333;
- line-height: 1.2;
}
- .metric-unit {
- font-size: 0.6em;
- color: #888;
- font-weight: normal;
+ .data-table td.num {
+ text-align: right;
+ font-family: 'Courier New', monospace;
+ font-size: 0.95em;
}
+ .data-table td.center { text-align: center; }
+ .data-table td.key { color: #666; font-weight: 500; width: 38%; }
+
+ .data-table tbody tr:nth-child(even) { background: #fafafa; }
+ .data-table tbody tr:hover { background: #f0f4ff; }
+
+ .iface-name { font-weight: bold; color: #2196f3; }
+
+ /* ── Percent bars ───────────────────────────────────────────── */
+
+ .bar-wrap {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .bar-track {
+ display: inline-block;
+ width: 70px;
+ height: 6px;
+ background: #e0e0e0;
+ border-radius: 3px;
+ vertical-align: middle;
+ flex-shrink: 0;
+ }
+
+ .bar-fill {
+ height: 6px;
+ border-radius: 3px;
+ background: #4caf50;
+ max-width: 100%;
+ }
+
+ .bar-fill.warn { background: #ff9800; }
+ .bar-fill.crit { background: #f44336; }
+
+ /* ── Disk two-table layout ──────────────────────────────────── */
+
+ .flex-tables {
+ display: flex;
+ gap: 14px;
+ flex-wrap: wrap;
+ }
+
+ .flex-tables > div { flex: 1 1 380px; }
+
+ .table-section-label {
+ font-size: 0.78em;
+ font-weight: 600;
+ text-transform: uppercase;
+ color: #888;
+ letter-spacing: 0.4px;
+ margin-bottom: 4px;
+ }
+
+ /* ── Status / misc ──────────────────────────────────────────── */
+
+ .status-up { color: #4caf50; font-weight: bold; }
+ .status-down { color: #f44336; font-weight: bold; }
+
+ .pct-ok { color: #2e7d32; font-weight: bold; }
+ .pct-warn { color: #e65100; font-weight: bold; }
+ .pct-crit { color: #b71c1c; font-weight: bold; }
+
+ .check-ok { background: #e8f5e9; }
+ .check-warning { background: #fff3e0; }
+ .check-critical { background: #ffebee; }
+ .check-unknown { background: #f5f5f5; }
+
+ .check-status-ok { color: #2e7d32; font-weight: bold; }
+ .check-status-warning { color: #e65100; font-weight: bold; }
+ .check-status-critical { color: #b71c1c; font-weight: bold; }
+ .check-status-unknown { color: #777; font-weight: bold; }
+
+ .check-output { font-size: 0.9em; color: #555; }
+
.timestamp {
- color: #999;
+ color: #bbb;
font-size: 0.75em;
- margin-top: 10px;
- padding-top: 8px;
- border-top: 1px solid #e0e0e0;
+ margin-top: 8px;
+ padding-top: 6px;
+ border-top: 1px solid #f0f0f0;
+ text-align: right;
}
.no-data {
text-align: center;
padding: 20px;
- color: #999;
+ color: #aaa;
font-style: italic;
font-size: 0.9em;
}
.loading {
text-align: center;
- padding: 15px;
- color: #666;
- font-size: 0.9em;
+ padding: 12px;
+ color: #aaa;
+ font-size: 0.85em;
}
.error {
background: #ffebee;
border-left: 3px solid #f44336;
- padding: 10px;
- margin: 10px 0;
+ padding: 8px 12px;
+ margin: 8px 0;
border-radius: 3px;
color: #c62828;
- font-size: 0.9em;
- }
-
- .nested-metrics {
- margin-top: 8px;
- padding-left: 12px;
- border-left: 2px solid #ddd;
- }
-
- .nested-header {
- font-weight: bold;
- color: #555;
- margin: 8px 0 5px 0;
font-size: 0.85em;
}
- /* Scrollbar styling */
- .container::-webkit-scrollbar {
- width: 8px;
- }
+ /* ── Scrollbar ──────────────────────────────────────────────── */
- .container::-webkit-scrollbar-track {
- background: #f1f1f1;
- border-radius: 4px;
- }
-
- .container::-webkit-scrollbar-thumb {
- background: #888;
- border-radius: 4px;
- }
-
- .container::-webkit-scrollbar-thumb:hover {
- background: #555;
- }
-
- /* Table styling for interface data */
- .interface-table {
- width: 100%;
- border-collapse: collapse;
- margin-top: 10px;
- font-size: 0.85em;
- background: #fff;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
- border-radius: 4px;
- overflow: hidden;
- }
-
- .interface-table thead {
- background: #2196f3;
- color: white;
- }
-
- .interface-table th {
- padding: 8px 10px;
- text-align: left;
- font-weight: 600;
- text-transform: uppercase;
- font-size: 0.75em;
- letter-spacing: 0.5px;
- }
-
- .interface-table th.number {
- text-align: right;
- }
-
- .interface-table td {
- padding: 6px 10px;
- border-top: 1px solid #e0e0e0;
- }
-
- .interface-table td.number {
- text-align: right;
- font-family: 'Courier New', monospace;
- }
-
- .interface-table tbody tr:hover {
- background: #f5f5f5;
- }
-
- .interface-table tbody tr:nth-child(even) {
- background: #fafafa;
- }
-
- .interface-table tbody tr:nth-child(even):hover {
- background: #f0f0f0;
- }
-
- .interface-name {
- font-weight: bold;
- color: #2196f3;
- }
-
- /* Simple data table styling (for os_info, cpu_monitor, etc.) */
- .simple-data-table {
- width: 100%;
- border-collapse: collapse;
- margin-top: 10px;
- font-size: 0.9em;
- background: #fff;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
- border-radius: 4px;
- overflow: hidden;
- }
-
- .simple-data-table thead {
- background: #2196f3;
- color: white;
- }
-
- .simple-data-table th {
- padding: 10px 15px;
- text-align: left;
- font-weight: 600;
- text-transform: uppercase;
- font-size: 0.8em;
- letter-spacing: 0.5px;
- }
-
- .simple-data-table td {
- padding: 8px 15px;
- border-top: 1px solid #e0e0e0;
- }
-
- .simple-data-table td.name {
- font-weight: 500;
- color: #555;
- width: 40%;
- }
-
- .simple-data-table td.value {
- color: #333;
- font-family: 'Segoe UI', system-ui, sans-serif;
- }
-
- .simple-data-table tbody tr:hover {
- background: #f5f5f5;
- }
-
- .simple-data-table tbody tr:nth-child(even) {
- background: #fafafa;
- }
-
- .simple-data-table tbody tr:nth-child(even):hover {
- background: #f0f0f0;
- }
+ .container::-webkit-scrollbar { width: 8px; }
+ .container::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
+ .container::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
+ .container::-webkit-scrollbar-thumb:hover { background: #999; }
@@ -332,732 +348,744 @@
{{ header }}
-
Real-time system metrics from monitoring plugins
+
Per-host system metrics — expand a host to see plugin details
{% if not hosts %}
No hosts with plugin data available
-
Hosts will appear here once they start sending plugin metrics
+
Hosts will appear here once they start sending plugin metrics
{% else %}
{% for host in hosts %}
-
+ {% set plugins_csv = host.plugins | join(',') %}
+
+
{% endfor %}
{% endif %}