diff --git a/app/web/static/js/app.js b/app/web/static/js/app.js new file mode 100644 index 0000000..304a68b --- /dev/null +++ b/app/web/static/js/app.js @@ -0,0 +1,242 @@ +/** + * LLM Monitor - Main App + * Gestisce il Web Worker e aggiorna il DOM in modo efficiente + */ + +class LLMMonitorApp { + constructor() { + this.worker = null; + this.lastData = { + health: null, + models: null + }; + this.init(); + } + + init() { + // Inizializzare il Web Worker + if (typeof Worker !== 'undefined') { + this.worker = new Worker('/static/js/data-sync.worker.js'); + this.worker.onmessage = (event) => this.handleWorkerMessage(event); + this.worker.onerror = (error) => { + console.error("Worker error:", error); + // Fallback: sincronizzazione nel main thread + this.syncDataInMainThread(); + }; + } else { + console.warn("Web Workers not supported, using main thread"); + this.syncDataInMainThread(); + } + + // Caricare dati da localStorage + this.loadFromLocalStorage(); + + // Listener al pulsante manuale + document.getElementById("refresh-btn")?.addEventListener("click", () => { + this.requestSync(); + }); + } + + // Caricare dati da localStorage + loadFromLocalStorage() { + const healthStr = localStorage.getItem("llm_monitor_health"); + const modelsStr = localStorage.getItem("llm_monitor_models"); + + if (healthStr) { + try { + this.lastData.health = JSON.parse(healthStr); + this.renderHealth(this.lastData.health); + } catch (e) { + console.error("Error parsing health data:", e); + } + } + + if (modelsStr) { + try { + this.lastData.models = JSON.parse(modelsStr); + this.renderModels(this.lastData.models); + } catch (e) { + console.error("Error parsing models data:", e); + } + } + } + + // Gestire messaggi dal Worker + handleWorkerMessage(event) { + const { type, health, modelsData } = event.data; + + if (type === "DATA_UPDATED") { + if (health && JSON.stringify(this.lastData.health) !== JSON.stringify(health)) { + this.lastData.health = health; + this.renderHealth(health); + } + + if (modelsData && JSON.stringify(this.lastData.models) !== JSON.stringify(modelsData)) { + this.lastData.models = modelsData; + this.renderModels(modelsData); + } + } + } + + // Renderizzare Health (aggiornamento granulare) + renderHealth(health) { + if (!health) return; + + const ollamaStatus = health.ollama_status; + const statusEl = document.getElementById("status-indicator"); + const statusText = document.getElementById("status-text"); + const ollamaStatusEl = document.getElementById("ollama-status"); + + if (ollamaStatus === "online") { + // Aggiornare solo se cambiato + if (!statusEl.classList.contains("bg-green-500")) { + statusEl.className = "w-3 h-3 bg-green-500 rounded-full"; + statusText.className = "text-sm text-green-400"; + statusText.textContent = "Ollama Online"; + ollamaStatusEl.innerHTML = "π’ Online"; + } + } else { + if (!statusEl.classList.contains("bg-red-500")) { + statusEl.className = "w-3 h-3 bg-red-500 rounded-full"; + statusText.className = "text-sm text-red-400"; + statusText.textContent = "Ollama Offline"; + ollamaStatusEl.innerHTML = "π΄ Offline"; + } + } + } + + // Renderizzare Modelli (aggiornamento granulare) + renderModels(modelsData) { + if (!modelsData) return; + + // Aggiornare conteggio + document.getElementById("models-count").textContent = modelsData.total; + + // Aggiornare spazio totale + document.getElementById("total-size").textContent = modelsData.totalSize; + + // Aggiornare lista modelli + const container = document.getElementById("models-container"); + const { models } = modelsData; + + if (models.length === 0) { + container.innerHTML = ` +
Nessun modello caricato
+Dimensione
+${this.formatBytes(model.size)}
+Ultimo aggiornamento
+${formattedDate}
+Digest
+${this.escapeHtml(model.digest.substring(0, 64))}...
+