feat: add multi-server control panel and host-aware sync
This commit is contained in:
+92
-10
@@ -6,6 +6,7 @@
|
||||
class LLMMonitorApp {
|
||||
constructor() {
|
||||
this.worker = null;
|
||||
this.activeServer = getActiveServer();
|
||||
this.selectedModelName = null;
|
||||
this.isModalOpen = false;
|
||||
this.hoverOpenDelayMs = 180;
|
||||
@@ -18,6 +19,13 @@ class LLMMonitorApp {
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.activeServer) {
|
||||
this.renderNoServerState();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateServerContextUI();
|
||||
|
||||
// Inizializzare il Web Worker
|
||||
if (typeof Worker !== 'undefined') {
|
||||
this.worker = new Worker('/static/js/data-sync.worker.js');
|
||||
@@ -27,6 +35,11 @@ class LLMMonitorApp {
|
||||
// Fallback: sincronizzazione nel main thread
|
||||
this.syncDataInMainThread();
|
||||
};
|
||||
this.worker.postMessage({
|
||||
type: "SET_SERVER",
|
||||
serverId: this.activeServer.id,
|
||||
host: this.activeServer.host
|
||||
});
|
||||
} else {
|
||||
console.warn("Web Workers not supported, using main thread");
|
||||
this.syncDataInMainThread();
|
||||
@@ -60,8 +73,8 @@ class LLMMonitorApp {
|
||||
|
||||
// Caricare dati da localStorage
|
||||
loadFromLocalStorage() {
|
||||
const healthStr = localStorage.getItem("llm_monitor_health");
|
||||
const modelsStr = localStorage.getItem("llm_monitor_models");
|
||||
const healthStr = localStorage.getItem(this.getStorageKey("health"));
|
||||
const modelsStr = localStorage.getItem(this.getStorageKey("models"));
|
||||
|
||||
if (healthStr) {
|
||||
try {
|
||||
@@ -84,13 +97,17 @@ class LLMMonitorApp {
|
||||
|
||||
// Gestire messaggi dal Worker
|
||||
handleWorkerMessage(event) {
|
||||
const { type, health, modelsData } = event.data;
|
||||
const { type, health, modelsData, serverId } = event.data;
|
||||
|
||||
if (serverId && this.activeServer && serverId !== this.activeServer.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "DATA_UPDATED") {
|
||||
if (health && JSON.stringify(this.lastData.health) !== JSON.stringify(health)) {
|
||||
this.lastData.health = health;
|
||||
try {
|
||||
localStorage.setItem("llm_monitor_health", JSON.stringify(health));
|
||||
localStorage.setItem(this.getStorageKey("health"), JSON.stringify(health));
|
||||
} catch (error) {
|
||||
console.warn("Cannot persist health in localStorage:", error);
|
||||
}
|
||||
@@ -100,7 +117,7 @@ class LLMMonitorApp {
|
||||
if (modelsData && JSON.stringify(this.lastData.models) !== JSON.stringify(modelsData)) {
|
||||
this.lastData.models = modelsData;
|
||||
try {
|
||||
localStorage.setItem("llm_monitor_models", JSON.stringify(modelsData));
|
||||
localStorage.setItem(this.getStorageKey("models"), JSON.stringify(modelsData));
|
||||
} catch (error) {
|
||||
console.warn("Cannot persist models in localStorage:", error);
|
||||
}
|
||||
@@ -326,6 +343,10 @@ class LLMMonitorApp {
|
||||
|
||||
// Chiedere sincronizzazione manuale al Worker
|
||||
requestSync() {
|
||||
if (!this.activeServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.worker) {
|
||||
this.worker.postMessage({ type: "SYNC_NOW" });
|
||||
} else {
|
||||
@@ -335,12 +356,16 @@ class LLMMonitorApp {
|
||||
|
||||
// Fallback: sincronizzazione nel main thread
|
||||
async syncDataInMainThread() {
|
||||
if (!this.activeServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/v1/health");
|
||||
const response = await fetch(this.buildApiUrl("/api/v1/health"));
|
||||
if (response.ok) {
|
||||
const health = await response.json();
|
||||
this.lastData.health = health;
|
||||
localStorage.setItem("llm_monitor_health", JSON.stringify(health));
|
||||
localStorage.setItem(this.getStorageKey("health"), JSON.stringify(health));
|
||||
this.renderHealth(health);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -348,7 +373,7 @@ class LLMMonitorApp {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/v1/models");
|
||||
const response = await fetch(this.buildApiUrl("/api/v1/models"));
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const models = data.models || [];
|
||||
@@ -356,7 +381,7 @@ class LLMMonitorApp {
|
||||
const showByModel = {};
|
||||
await Promise.allSettled(
|
||||
models.map(async (model) => {
|
||||
const showResponse = await fetch(`/api/v1/models/${encodeURIComponent(model.name)}/show`);
|
||||
const showResponse = await fetch(this.buildApiUrl(`/api/v1/models/${encodeURIComponent(model.name)}/show`));
|
||||
if (showResponse.ok) {
|
||||
showByModel[model.name] = await showResponse.json();
|
||||
}
|
||||
@@ -371,7 +396,7 @@ class LLMMonitorApp {
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
this.lastData.models = modelsData;
|
||||
localStorage.setItem("llm_monitor_models", JSON.stringify(modelsData));
|
||||
localStorage.setItem(this.getStorageKey("models"), JSON.stringify(modelsData));
|
||||
this.renderModels(modelsData);
|
||||
if (this.selectedModelName) {
|
||||
this.showModelDetails(this.selectedModelName);
|
||||
@@ -381,6 +406,63 @@ class LLMMonitorApp {
|
||||
console.error("Models loading error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
getStorageKey(suffix) {
|
||||
return `llm_monitor_${suffix}_${this.activeServer.id}`;
|
||||
}
|
||||
|
||||
buildApiUrl(path) {
|
||||
const url = new URL(path, window.location.origin);
|
||||
url.searchParams.set("host", this.activeServer.host);
|
||||
return `${url.pathname}${url.search}`;
|
||||
}
|
||||
|
||||
updateServerContextUI() {
|
||||
const serverLabel = document.getElementById("active-server-label");
|
||||
if (serverLabel) {
|
||||
serverLabel.textContent = `Server: ${this.activeServer.name}`;
|
||||
serverLabel.classList.remove("hidden");
|
||||
}
|
||||
|
||||
const runningLink = document.getElementById("running-link");
|
||||
if (runningLink) {
|
||||
runningLink.href = buildServerUrl("/models-running", this.activeServer.id);
|
||||
}
|
||||
|
||||
const serversLink = document.getElementById("servers-link");
|
||||
if (serversLink) {
|
||||
serversLink.href = "/servers";
|
||||
}
|
||||
}
|
||||
|
||||
renderNoServerState() {
|
||||
const container = document.getElementById("models-container");
|
||||
const count = document.getElementById("models-count");
|
||||
const totalSize = document.getElementById("total-size");
|
||||
const statusIndicator = document.getElementById("status-indicator");
|
||||
const statusText = document.getElementById("status-text");
|
||||
const ollamaStatus = document.getElementById("ollama-status");
|
||||
|
||||
if (count) count.textContent = "0";
|
||||
if (totalSize) totalSize.textContent = "0 B";
|
||||
if (statusIndicator) statusIndicator.className = "w-3 h-3 bg-yellow-500 rounded-full";
|
||||
if (statusText) {
|
||||
statusText.className = "text-sm text-yellow-300";
|
||||
statusText.textContent = "No server selected";
|
||||
}
|
||||
if (ollamaStatus) {
|
||||
ollamaStatus.innerHTML = "🟡 Not configured";
|
||||
}
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-10 text-gray-300">
|
||||
<p class="text-lg font-semibold">No server selected</p>
|
||||
<p class="text-sm text-gray-400 mt-2">Configure or select a server from the control panel.</p>
|
||||
<a href="/servers" class="inline-block mt-4 bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded">Open Servers Control Panel</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inizializzare l'app quando il DOM è pronto
|
||||
|
||||
Reference in New Issue
Block a user