Types, types, types...

This commit is contained in:
Kalzu Rekku
2025-06-14 01:19:57 +03:00
parent 44c13c16df
commit c98081d360
7 changed files with 87 additions and 27 deletions

View File

@ -76,3 +76,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
# Run the application using the virtual environment's python interpreter # Run the application using the virtual environment's python interpreter
CMD ["/opt/venv/bin/python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips", "*"] CMD ["/opt/venv/bin/python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", "--forwarded-allow-ips", "*"]
#CMD ["/opt/venv/bin/python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@ -346,6 +346,7 @@ async def read_root(request: Request):
"uptime_seconds": data.get("uptime_seconds"), "uptime_seconds": data.get("uptime_seconds"),
"formatted_uptime": format_uptime(data.get("uptime_seconds")), # Pre-format uptime for HTML "formatted_uptime": format_uptime(data.get("uptime_seconds")), # Pre-format uptime for HTML
"load_avg": data.get("load_avg"), "load_avg": data.get("load_avg"),
"load_avg_fmt": [f"{x:.2f}" for x in data.get("load_avg", [])] if data.get("load_avg") else None,
"memory_usage_percent": data.get("memory_usage_percent"), "memory_usage_percent": data.get("memory_usage_percent"),
"connections": connections, "connections": connections,
} }

View File

@ -4,12 +4,47 @@ document.addEventListener('DOMContentLoaded', () => {
const levelRadios = document.querySelectorAll('input[name="log-level"]'); const levelRadios = document.querySelectorAll('input[name="log-level"]');
const sinceFilter = document.getElementById('since-filter'); const sinceFilter = document.getElementById('since-filter');
const applyFiltersButton = document.getElementById('apply-filters'); const applyFiltersButton = document.getElementById('apply-filters');
const togglePollingButton = document.getElementById('toggle-polling');
const pollingStatusSpan = document.getElementById('polling-status');
const POLLING_INTERVAL_MS = 5000; const POLLING_INTERVAL_MS = 5000;
const serviceUuid = logTableContainer.dataset.serviceUuid; // Get service UUID from data attribute const serviceUuid = logTableContainer.dataset.serviceUuid; // Get service UUID from data attribute
let currentLevel = ''; let currentLevel = '';
let currentSince = ''; let currentSince = '';
let pollingIntervalId = null;
let isPollingActive = true; // Start active by default
// Function to update the UI elements related to polling status
function updatePollingStatusUI() {
if (isPollingActive) {
togglePollingButton.textContent = 'Pause Auto-Refresh';
pollingStatusSpan.textContent = `Auto-refresh: ON (every ${POLLING_INTERVAL_MS / 1000} seconds)`;
} else {
togglePollingButton.textContent = 'Resume Auto-Refresh';
pollingStatusSpan.textContent = 'Auto-refresh: OFF (paused)';
}
}
// Function to start polling
function startPolling() {
if (pollingIntervalId) {
clearInterval(pollingIntervalId); // Clear any existing interval to prevent duplicates
}
isPollingActive = true;
fetchLogs(); // Fetch immediately when starting/resuming
pollingIntervalId = setInterval(fetchLogs, POLLING_INTERVAL_MS);
updatePollingStatusUI();
}
// Function to stop polling
function stopPolling() {
isPollingActive = false;
clearInterval(pollingIntervalId);
pollingIntervalId = null;
updatePollingStatusUI();
}
async function fetchLogs() { async function fetchLogs() {
console.log('Fetching logs with params:', { level: currentLevel, since: currentSince }); console.log('Fetching logs with params:', { level: currentLevel, since: currentSince });
try { try {
@ -24,13 +59,11 @@ document.addEventListener('DOMContentLoaded', () => {
console.log('Fetch URL:', url); console.log('Fetch URL:', url);
const response = await fetch(url); const response = await fetch(url);
console.log('Response status:', response.status); console.log('Response status:', response.status);
console.log('Response Content-Type:', response.headers.get('Content-Type')); // NEW: Log Content-Type console.log('Response Content-Type:', response.headers.get('Content-Type'));
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); // Try to get response body as text const errorText = await response.text();
console.error('Raw response text on error:', errorText.substring(0, 500) + (errorText.length > 500 ? '...' : '')); // Log first 500 chars console.error('Raw response text on error:', errorText.substring(0, 500) + (errorText.length > 500 ? '...' : ''));
// If the server returns a 404, it might be due to a stale UUID.
// Log a more specific message.
if (response.status === 404) { 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.`); console.error(`404 Not Found: Service UUID mismatch. Current page UUID: ${serviceUuid}. Server UUID might have changed. Please hard refresh the page.`);
logTableContainer.innerHTML = `<p class="loading-message error-message">Error: Service UUID mismatch. Please hard refresh your browser tab (Ctrl+F5 or Cmd+Shift+R).</p>`; logTableContainer.innerHTML = `<p class="loading-message error-message">Error: Service UUID mismatch. Please hard refresh your browser tab (Ctrl+F5 or Cmd+Shift+R).</p>`;
@ -39,7 +72,8 @@ document.addEventListener('DOMContentLoaded', () => {
console.error(`HTTP error! status: ${response.status} - ${errorText}`); console.error(`HTTP error! status: ${response.status} - ${errorText}`);
logTableContainer.innerHTML = `<p class="loading-message error-message">Error loading logs. HTTP Status: ${response.status}. Please check server connection or hard refresh.</p>`; logTableContainer.innerHTML = `<p class="loading-message error-message">Error loading logs. HTTP Status: ${response.status}. Please check server connection or hard refresh.</p>`;
} }
return; // Stop further processing if error stopPolling(); // Stop polling on error
return;
} }
// Attempt to parse JSON. This is where the error would occur if the content is HTML. // Attempt to parse JSON. This is where the error would occur if the content is HTML.
@ -50,6 +84,7 @@ document.addEventListener('DOMContentLoaded', () => {
} catch (error) { } catch (error) {
console.error("Error fetching logs (JSON parsing or other):", error); console.error("Error fetching logs (JSON parsing or other):", error);
logTableContainer.innerHTML = '<p class="loading-message error-message">Error loading logs. Failed to parse response or other network issue. See console for details.</p>'; logTableContainer.innerHTML = '<p class="loading-message error-message">Error loading logs. Failed to parse response or other network issue. See console for details.</p>';
stopPolling(); // Stop polling on error
} }
} }
@ -151,19 +186,26 @@ document.addEventListener('DOMContentLoaded', () => {
return div.innerHTML; return div.innerHTML;
} }
// Event listener for the "Apply Filters" button
applyFiltersButton.addEventListener('click', () => { applyFiltersButton.addEventListener('click', () => {
const selectedRadio = document.querySelector('input[name="log-level"]:checked'); const selectedRadio = document.querySelector('input[name="log-level"]:checked');
currentLevel = selectedRadio ? selectedRadio.value : ''; currentLevel = selectedRadio ? selectedRadio.value : '';
// 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; currentSince = sinceFilter.value;
console.log('Applying filters:', { level: currentLevel, since: currentSince }); console.log('Applying filters:', { level: currentLevel, since: currentSince });
fetchLogs(); // When filters are applied, always restart polling to fetch new data
startPolling();
});
// Event listener for the new "Toggle Polling" button
togglePollingButton.addEventListener('click', () => {
if (isPollingActive) {
stopPolling();
} else {
startPolling();
}
}); });
console.log('Initializing logs page'); console.log('Initializing logs page');
// Call fetchLogs immediately on page load to populate the table with fresh data // Initial call to start polling when the page loads
// and handle the initial refresh logic. startPolling();
fetchLogs();
setInterval(fetchLogs, POLLING_INTERVAL_MS);
}); });

View File

@ -72,16 +72,30 @@ document.addEventListener('DOMContentLoaded', () => {
<p><strong>UUID:</strong> ${rowNode.uuid}</p> <p><strong>UUID:</strong> ${rowNode.uuid}</p>
<p><strong>IP:</strong> ${rowNode.ip}</p> <p><strong>IP:</strong> ${rowNode.ip}</p>
<p><strong>Last Seen:</strong> ${new Date(rowNode.last_seen).toLocaleTimeString()}</p> <p><strong>Last Seen:</strong> ${new Date(rowNode.last_seen).toLocaleTimeString()}</p>
<p><strong>Uptime:</strong> ${rowNode.uptime_seconds ? formatUptime(rowNode.uptime_seconds) : 'N/A'}</p> <p><strong>Uptime:</strong> ${rowNode.uptime_seconds =! null ? formatUptime(rowNode.uptime_seconds) : 'N/A'}</p>
<p><strong>Load Avg (1m, 5m, 15m):</strong> ${rowNode.load_avg ? rowNode.load_avg.map(l => l.toFixed(2)).join(', ') : 'N/A'}</p> <p><strong>Load Avg (1m, 5m, 15m):</strong> ${
<p><strong>Memory Usage:</strong> ${rowNode.memory_usage_percent ? rowNode.memory_usage_percent.toFixed(2) + '%' : 'N/A'}</p> Array.isArray(rowNode.load_avg)
? rowNode.load_avg.map(l => l.toFixed(2)).join(', ')
: 'N/A'
}</p>
<p><strong>Memory Usage:</strong> ${
rowNode.memory_usage_percent != null
? rowNode.memory_usage_percent.toFixed(2) + '%'
: 'N/A'
}</p>
</div> </div>
`; `;
} else { } else {
// Off-diagonal: show ping latency // Off-diagonal: show ping latency
const latency = rowNode.connections && colNode.uuid in rowNode.connections && rowNode.connections[colNode.uuid] !== null ? rowNode.connections[colNode.uuid] : null; const latency = (
const displayLatency = latency !== null && !isNaN(latency) ? `${latency.toFixed(1)} ms` : 'N/A'; rowNode.connections &&
const latencyClass = latency !== null && !isNaN(latency) ? getLatencyClass(latency) : 'latency-unavailable'; colNode.uuid in rowNode.connections &&
rowNode.connections[colNode.uuid] !== null
) ? rowNode.connections[colNode.uuid] : null;
const displayLatency = (latency != null && !isNaN(latency)) ? `${latency.toFixed(1)} ms` : 'N/A';
const latencyClass = (latency != null && !isNaN(latency)) ? getLatencyClass(latency) : 'latency-unavailable';
cell.classList.add(latencyClass); cell.classList.add(latencyClass);
cell.innerHTML = ` cell.innerHTML = `
<div class="conn-status-text">Ping: ${displayLatency}</div> <div class="conn-status-text">Ping: ${displayLatency}</div>

View File

@ -58,7 +58,7 @@
<p><strong>IP:</strong> {{ row_node.ip }}</p> <p><strong>IP:</strong> {{ row_node.ip }}</p>
<p><strong>Last Seen:</strong> {{ row_node.formatted_last_seen }}</p> <p><strong>Last Seen:</strong> {{ row_node.formatted_last_seen }}</p>
<p><strong>Uptime:</strong> {{ row_node.formatted_uptime }}</p> <p><strong>Uptime:</strong> {{ row_node.formatted_uptime }}</p>
<p><strong>Load Avg (1m, 5m, 15m):</strong> {{ row_node.load_avg | map('%.2f' | format) | join(', ') if row_node.load_avg else 'N/A' }}</p> <p><strong>Load Avg (1m, 5m, 15m):</strong> {{ row_node.load_avg_fmt | join(', ') if row_node.load_avg_fmt else 'N/A' }}</p>
<p><strong>Memory Usage:</strong> {{ '%.2f' | format(row_node.memory_usage_percent) + '%' if row_node.memory_usage_percent is not none else 'N/A' }}</p> <p><strong>Memory Usage:</strong> {{ '%.2f' | format(row_node.memory_usage_percent) + '%' if row_node.memory_usage_percent is not none else 'N/A' }}</p>
</div> </div>
{% else %} {% else %}

View File

@ -9,7 +9,7 @@
<div class="header-container"> <div class="header-container">
<h1>Node Monitor Logs</h1> <h1>Node Monitor Logs</h1>
<p>Service UUID: <code>{{ service_uuid }}</code></p> <p>Service UUID: <code>{{ service_uuid }}</code></p>
<p>Total Logs: <span id="log-count">{{ log_count }}</span> (Auto-refreshing every 5 seconds)</p> <p>Total Logs: <span id="log-count">{{ log_count }}</span> (<span id="polling-status"></span>)</p>
<div class="filter-container"> <div class="filter-container">
<fieldset> <fieldset>
<legend>Log Level</legend> <legend>Log Level</legend>
@ -21,6 +21,7 @@
<label for="since-filter">Since (UTC ISO 8601):</label> <label for="since-filter">Since (UTC ISO 8601):</label>
<input type="datetime-local" id="since-filter" placeholder="e.g., 2025-06-11T13:32 (UTC)"> <input type="datetime-local" id="since-filter" placeholder="e.g., 2025-06-11T13:32 (UTC)">
<button id="apply-filters">Apply</button> <button id="apply-filters">Apply</button>
<button id="toggle-polling"></button> {# Added toggle-polling button #}
</div> </div>
</div> </div>
<div id="log-table-container" data-service-uuid="{{ service_uuid }}"> <div id="log-table-container" data-service-uuid="{{ service_uuid }}">

View File

@ -20,6 +20,7 @@ services:
environment: environment:
# Set a fixed SERVICE_UUID here. Replace this with your desired UUID. # Set a fixed SERVICE_UUID here. Replace this with your desired UUID.
# This UUID will be used by the FastAPI app and passed to the frontend. # This UUID will be used by the FastAPI app and passed to the frontend.
# !! David, change this !!
SERVICE_UUID: "ab73d00a-8169-46bb-997d-f13e5f760973" SERVICE_UUID: "ab73d00a-8169-46bb-997d-f13e5f760973"
DATA_DIR: "/data" # Inform the application where its data volume is mounted DATA_DIR: "/data" # Inform the application where its data volume is mounted
@ -27,9 +28,9 @@ services:
restart: unless-stopped restart: unless-stopped
# Healthcheck to ensure the container is running and responsive # Healthcheck to ensure the container is running and responsive
healthcheck: #healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8000/health"] # test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8000/health"]
interval: 30s # interval: 30s
timeout: 10s # timeout: 10s
start_period: 5s # start_period: 5s
retries: 3 # retries: 3