feat: enhance System Health dashboard with rich telemetry and visualizations

Add SVG arc gauge, per-core CPU bars, temperature sparkline, network
interface monitoring with bandwidth deltas, disk I/O rates, 3D globe
with observer location, weather overlay, battery/fan/throttle support,
and process grid layout. New /system/location and /system/weather endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-26 23:42:45 +00:00
parent 32ce758172
commit bad2110b92
6 changed files with 1344 additions and 87 deletions
+103
View File
@@ -30,6 +30,32 @@ def test_metrics_returns_expected_keys(client):
assert 'uptime_human' in data['system']
def test_metrics_enhanced_keys(client):
"""GET /system/metrics returns enhanced metric keys."""
_login(client)
resp = client.get('/system/metrics')
assert resp.status_code == 200
data = resp.get_json()
# New enhanced keys
assert 'network' in data
assert 'disk_io' in data
assert 'boot_time' in data
assert 'battery' in data
assert 'fans' in data
assert 'power' in data
# CPU should have per_core and freq
if data['cpu'] is not None:
assert 'per_core' in data['cpu']
assert 'freq' in data['cpu']
# Network should have interfaces and connections
if data['network'] is not None:
assert 'interfaces' in data['network']
assert 'connections' in data['network']
assert 'io' in data['network']
def test_metrics_without_psutil(client):
"""Metrics degrade gracefully when psutil is unavailable."""
_login(client)
@@ -45,6 +71,11 @@ def test_metrics_without_psutil(client):
assert data['cpu'] is None
assert data['memory'] is None
assert data['disk'] is None
assert data['network'] is None
assert data['disk_io'] is None
assert data['battery'] is None
assert data['boot_time'] is None
assert data['power'] is None
finally:
mod._HAS_PSUTIL = orig
@@ -87,3 +118,75 @@ def test_stream_returns_sse_content_type(client):
resp = client.get('/system/stream')
assert resp.status_code == 200
assert 'text/event-stream' in resp.content_type
def test_location_returns_shape(client):
"""GET /system/location returns lat/lon/source shape."""
_login(client)
with patch('routes.system.contextlib.suppress'):
resp = client.get('/system/location')
assert resp.status_code == 200
data = resp.get_json()
assert 'lat' in data
assert 'lon' in data
assert 'source' in data
def test_location_falls_back_to_config(client):
"""Location endpoint returns config defaults when GPS unavailable."""
_login(client)
with patch('routes.system.DEFAULT_LATITUDE', 40.7128, create=True), \
patch('routes.system.DEFAULT_LONGITUDE', -74.006, create=True):
# Mock the import inside _get_observer_location
resp = client.get('/system/location')
assert resp.status_code == 200
data = resp.get_json()
assert 'source' in data
def test_weather_requires_location(client):
"""Weather endpoint returns error when no location available."""
_login(client)
# Without lat/lon params and no GPS state or config
resp = client.get('/system/weather')
assert resp.status_code == 200
data = resp.get_json()
# Either returns weather or error (depending on config)
assert 'error' in data or 'temp_c' in data
def test_weather_with_mocked_response(client):
"""Weather endpoint returns parsed weather data with mocked HTTP."""
_login(client)
mock_resp = MagicMock()
mock_resp.status_code = 200
mock_resp.json.return_value = {
'current_condition': [{
'temp_C': '22',
'temp_F': '72',
'weatherDesc': [{'value': 'Clear'}],
'humidity': '45',
'windspeedMiles': '8',
'winddir16Point': 'NW',
'FeelsLikeC': '20',
'visibility': '10',
'pressure': '1013',
}]
}
mock_resp.raise_for_status = MagicMock()
import routes.system as mod
# Clear cache
mod._weather_cache.clear()
mod._weather_cache_time = 0.0
with patch('routes.system._requests') as mock_requests:
mock_requests.get.return_value = mock_resp
resp = client.get('/system/weather?lat=40.7&lon=-74.0')
assert resp.status_code == 200
data = resp.get_json()
assert data['temp_c'] == '22'
assert data['condition'] == 'Clear'
assert data['humidity'] == '45'
assert data['wind_mph'] == '8'