diff --git a/app/main.py b/app/main.py index 41c07de..16948ab 100644 --- a/app/main.py +++ b/app/main.py @@ -2,7 +2,7 @@ import os import uuid import json import logging -from datetime import datetime, timezone +from datetime import datetime, timezone, timedelta from fastapi import FastAPI, Request, status, Query from fastapi.responses import HTMLResponse, JSONResponse from fastapi.templating import Jinja2Templates @@ -12,6 +12,7 @@ from typing import Dict, List, Annotated import uuid as uuid_lib from collections import deque +from dateutil.parser import isoparse from pythonjsonlogger import jsonlogger import sys @@ -61,7 +62,7 @@ class LogBuffer: # Apply level filter if level and level.strip(): level = level.upper() - valid_levels = {'INFO', 'WARNING', 'ERROR', 'DEBUG'} # Added DEBUG for completeness + valid_levels = {'INFO', 'WARNING', 'ERROR', 'DEBUG'} if level in valid_levels: logs = [log for log in logs if log['level'].upper() == level] else: @@ -69,8 +70,16 @@ class LogBuffer: # Apply since filter if since: try: - # Handle 'Z' for UTC and ensure timezone awareness for comparison - since_dt = datetime.fromisoformat(since.replace('Z', '+00:00')).astimezone(timezone.utc) + # Use isoparse for robust parsing of ISO 8601 strings + since_dt = isoparse(since) + + # If the parsed datetime is naive (no timezone info), assume it's UTC + if since_dt.tzinfo is None: + since_dt = since_dt.replace(tzinfo=timezone.utc) + else: + # If it has timezone info, convert it to UTC for consistent comparison + since_dt = since_dt.astimezone(timezone.utc) + logs = [log for log in logs if datetime.fromisoformat(log['timestamp'].replace('Z', '+00:00')).astimezone(timezone.utc) >= since_dt] except ValueError: @@ -90,6 +99,13 @@ if not logger.handlers: logger.addHandler(logHandler) logger.addHandler(BufferHandler()) +# Configure Uvicorn's loggers to propagate to the root logger +# This ensures Uvicorn's startup and access logs are captured by our BufferHandler +logging.getLogger("uvicorn").propagate = True +logging.getLogger("uvicorn.access").propagate = True +logging.getLogger("uvicorn.error").propagate = True + + # --- FastAPI Application --- app = FastAPI( title="Node Monitoring System", @@ -139,6 +155,7 @@ LOAD_AVG_CRITICAL_THRESHOLD = 3.0 MEMORY_WARNING_THRESHOLD = 75.0 MEMORY_CRITICAL_THRESHOLD = 90.0 LAST_SEEN_CRITICAL_THRESHOLD_SECONDS = 30 +NODE_INACTIVE_REMOVAL_THRESHOLD_SECONDS = 300 # Remove node from UI after 5 minutes of inactivity def get_node_health(node_data: Dict) -> str: last_seen_str = node_data.get("last_seen") @@ -330,13 +347,25 @@ async def update_node_status( @app.get("/nodes/status") async def get_all_nodes_status(): logger.info("Fetching all nodes status for UI.") - response_nodes = [] + + # Prune inactive nodes from known_nodes_db before processing + current_time_utc = datetime.now(timezone.utc) + nodes_to_remove = [] + for node_uuid, data in known_nodes_db.items(): + last_seen_dt = datetime.fromisoformat(data["last_seen"]).replace(tzinfo=timezone.utc) + if (current_time_utc - last_seen_dt).total_seconds() > NODE_INACTIVE_REMOVAL_THRESHOLD_SECONDS: + nodes_to_remove.append(node_uuid) + logger.info(f"Removing inactive node {node_uuid} from known_nodes_db.") + for node_uuid in nodes_to_remove: + known_nodes_db.pop(node_uuid) + + response_nodes = [] for node_uuid, data in known_nodes_db.items(): current_health = get_node_health(data) connections = {} - for target_uuid in known_nodes_db: + for target_uuid in known_nodes_db: # Only iterate over currently active nodes if target_uuid != node_uuid: ping_data = database.get_ping_data(node_uuid, target_uuid, start_time="-300s") latency_ms = None @@ -363,3 +392,4 @@ async def get_all_nodes_status(): @app.get("/health") async def health_check(): return {"status": "ok"} +# --- END OF FILE main.py --- diff --git a/app/web/static/logs.js b/app/web/static/logs.js index 9653288..1b3dce6 100644 --- a/app/web/static/logs.js +++ b/app/web/static/logs.js @@ -25,15 +25,27 @@ document.addEventListener('DOMContentLoaded', () => { const response = await fetch(url); console.log('Response status:', response.status); if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + const errorText = await response.text(); // Try to get response body as text + console.error('Response text on error:', errorText); // Log it + // If the server returns a 404, it might be due to a stale UUID. + // Log a more specific message. + if (response.status === 404) { + console.error(`404 Not Found: Service UUID mismatch. Current page UUID: ${serviceUuid}. Server UUID might have changed. Please hard refresh the page.`); + logTableContainer.innerHTML = `

Error: Service UUID mismatch. Please hard refresh your browser tab (Ctrl+F5 or Cmd+Shift+R).

`; + } else { + // Log the status code to help debug + console.error(`HTTP error! status: ${response.status} - ${errorText}`); + logTableContainer.innerHTML = `

Error loading logs. HTTP Status: ${response.status}. Please check server connection or hard refresh.

`; + } + return; // Stop further processing if error } const data = await response.json(); console.log('Received logs:', data.logs.length); renderLogTable(data.logs); logCountSpan.textContent = data.log_count; } catch (error) { - console.error("Error fetching logs:", error); - logTableContainer.innerHTML = '

Error loading logs. Please check server connection.

'; + console.error("Error fetching logs (JSON parsing or other):", error); + logTableContainer.innerHTML = '

Error loading logs. Failed to parse response or other network issue. See console for details.

'; } } @@ -138,9 +150,9 @@ document.addEventListener('DOMContentLoaded', () => { applyFiltersButton.addEventListener('click', () => { const selectedRadio = document.querySelector('input[name="log-level"]:checked'); currentLevel = selectedRadio ? selectedRadio.value : ''; - const sinceValue = sinceFilter.value; - // Convert local datetime input to ISO string for backend, handling potential timezone issues - currentSince = sinceValue ? new Date(sinceValue).toISOString().replace(/\.\d{3}Z$/, 'Z') : ''; // Ensure 'Z' for UTC + // Send the datetime-local value directly. It's already in YYYY-MM-DDTHH:mm (24-hour) format. + // The backend will interpret this as UTC. + currentSince = sinceFilter.value; console.log('Applying filters:', { level: currentLevel, since: currentSince }); fetchLogs(); }); diff --git a/app/web/static/script.js b/app/web/static/script.js index 670205f..398b49b 100644 --- a/app/web/static/script.js +++ b/app/web/static/script.js @@ -19,8 +19,8 @@ document.addEventListener('DOMContentLoaded', () => { } function renderNodeGrid(nodes) { - nodeGridContainer.innerHTML = ''; // Clear existing content - nodeCountSpan.textContent = nodes.length; // Update total node count + nodeGridContainer.innerHTML = ''; + nodeCountSpan.textContent = nodes.length; if (nodes.length === 0) { nodeGridContainer.innerHTML = '

No nodes reporting yet. Start a client!

'; @@ -132,3 +132,4 @@ document.addEventListener('DOMContentLoaded', () => { fetchNodeData(); setInterval(fetchNodeData, POLLING_INTERVAL_MS); }); + diff --git a/app/web/static/style.css b/app/web/static/style.css index 6645e7a..0fe9ab4 100644 --- a/app/web/static/style.css +++ b/app/web/static/style.css @@ -40,6 +40,8 @@ body { margin-bottom: 20px; width: 80vw; max-width: 1200px; + margin-left: auto; + margin-right: auto; } h1 { @@ -63,13 +65,17 @@ code { } #node-grid-container, #log-table-container { - width: 80vw; - max-width: 1200px; + width: 95vw; + max-width: 1600px; min-width: 400px; padding: 20px; background-color: var(--nord3); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + margin-bottom: 20px; + margin-left: auto; + margin-right: auto; + overflow-x: auto; } .connection-grid { diff --git a/app/web/templates/index.html b/app/web/templates/index.html index 857977f..bb3aed1 100644 --- a/app/web/templates/index.html +++ b/app/web/templates/index.html @@ -10,7 +10,7 @@

Node Monitoring System

Total Nodes: 0

-

Service UUID: {{ service_uuid }}

+

Service UUID: {{ service_uuid }}

diff --git a/app/web/templates/logs.html b/app/web/templates/logs.html index d4337f0..5e53a75 100644 --- a/app/web/templates/logs.html +++ b/app/web/templates/logs.html @@ -2,7 +2,6 @@ - Node Monitor Logs - {{ service_uuid }} @@ -19,8 +18,8 @@ - - + +
@@ -64,3 +63,4 @@ + diff --git a/requirements.txt b/requirements.txt index 1a89758..cd802f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ rrdtool==0.1.16 jinja2==3.1.2 python-multipart==0.0.6 python-json-logger==2.0.7 +python-dateutil==2.9.0.post0 +