Translate UI to English and add PWA support
This commit is contained in:
@@ -156,7 +156,7 @@ class LLMMonitorApp {
|
||||
if (models.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-8 text-gray-400">
|
||||
<p>Nessun modello caricato</p>
|
||||
<p>No models loaded</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -229,15 +229,15 @@ class LLMMonitorApp {
|
||||
<div data-model-key="${modelKey}" class="bg-gray-700 rounded-lg p-4 border border-gray-600 hover:border-purple-500 hover:-translate-y-0.5 transition cursor-pointer h-full">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<h3 class="text-lg font-semibold">${modelName}</h3>
|
||||
<span class="bg-purple-600 px-3 py-1 rounded text-xs font-medium">Caricato</span>
|
||||
<span class="bg-purple-600 px-3 py-1 rounded text-xs font-medium">Loaded</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p class="text-gray-400">Dimensione</p>
|
||||
<p class="text-gray-400">Size</p>
|
||||
<p class="font-semibold">${this.formatBytes(model.size)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-400">Ultimo aggiornamento</p>
|
||||
<p class="text-gray-400">Last Updated</p>
|
||||
<p class="font-semibold">${formattedDate}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -245,7 +245,7 @@ class LLMMonitorApp {
|
||||
<p class="text-gray-400 text-xs">Digest</p>
|
||||
<p class="font-mono text-xs bg-gray-800 p-2 rounded mt-1 break-all">${this.escapeHtml(model.digest.substring(0, 64))}...</p>
|
||||
</div>
|
||||
<p class="text-xs text-purple-300 mt-3">Passa il mouse o clicca per vedere dettagli show</p>
|
||||
<p class="text-xs text-purple-300 mt-3">Hover or click to view show details</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -273,7 +273,7 @@ class LLMMonitorApp {
|
||||
detailsModal.setAttribute("aria-hidden", "false");
|
||||
|
||||
if (!showData) {
|
||||
detailsContent.textContent = "Dettagli show non disponibili per questo modello.";
|
||||
detailsContent.textContent = "Show details are not available for this model.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ class LLMMonitorApp {
|
||||
// Formattare data
|
||||
formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString("it-IT", {
|
||||
return date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
|
||||
@@ -37,7 +37,7 @@ async function fetchHealth() {
|
||||
async function fetchModels() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/models`);
|
||||
if (!response.ok) throw new Error("Errore nel caricamento");
|
||||
if (!response.ok) throw new Error("Failed to load models");
|
||||
|
||||
const data = await response.json();
|
||||
const models = data.models || [];
|
||||
|
||||
@@ -20,14 +20,14 @@ class RunningModelsPage {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<div class="inline-block w-8 h-8 border-4 border-gray-600 border-t-purple-500 rounded-full animate-spin"></div>
|
||||
<p class="text-gray-400 mt-4">Aggiornamento in corso...</p>
|
||||
<p class="text-gray-400 mt-4">Refreshing data...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/v1/models/running");
|
||||
if (!response.ok) {
|
||||
throw new Error("Errore nel caricamento dei modelli residenti");
|
||||
throw new Error("Failed to load running models");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
@@ -38,7 +38,7 @@ class RunningModelsPage {
|
||||
} catch (error) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-8 text-red-400">
|
||||
<p>Errore nel caricamento di ollama ps</p>
|
||||
<p>Failed to load ollama ps output</p>
|
||||
</div>
|
||||
`;
|
||||
this.renderStats([]);
|
||||
@@ -60,7 +60,7 @@ class RunningModelsPage {
|
||||
vramTotalEl.textContent = this.formatBytes(totalVram);
|
||||
}
|
||||
if (lastRefreshEl) {
|
||||
lastRefreshEl.textContent = new Date().toLocaleString("it-IT");
|
||||
lastRefreshEl.textContent = new Date().toLocaleString("en-US");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class RunningModelsPage {
|
||||
if (models.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-8 text-gray-400">
|
||||
<p>Nessun modello residente in memoria al momento.</p>
|
||||
<p>No models are currently loaded in memory.</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -99,15 +99,15 @@ class RunningModelsPage {
|
||||
<h3 class="text-lg font-semibold">${name}</h3>
|
||||
<p class="text-xs text-gray-400 mt-1">${modelId}</p>
|
||||
</div>
|
||||
<span class="bg-green-700 text-green-100 text-xs px-2 py-1 rounded">Pronto</span>
|
||||
<span class="bg-green-700 text-green-100 text-xs px-2 py-1 rounded">Ready</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mt-4 text-sm">
|
||||
<div class="bg-gray-800 rounded p-3">
|
||||
<p class="text-gray-400 text-xs">Dimensione modello</p>
|
||||
<p class="text-gray-400 text-xs">Model Size</p>
|
||||
<p class="font-semibold mt-1">${size}</p>
|
||||
</div>
|
||||
<div class="bg-gray-800 rounded p-3">
|
||||
<p class="text-gray-400 text-xs">VRAM usata</p>
|
||||
<p class="text-gray-400 text-xs">VRAM Used</p>
|
||||
<p class="font-semibold mt-1">${sizeVram}</p>
|
||||
</div>
|
||||
<div class="bg-gray-800 rounded p-3">
|
||||
@@ -115,7 +115,7 @@ class RunningModelsPage {
|
||||
<p class="font-semibold mt-1">${processor}</p>
|
||||
</div>
|
||||
<div class="bg-gray-800 rounded p-3">
|
||||
<p class="text-gray-400 text-xs">Scarico previsto</p>
|
||||
<p class="text-gray-400 text-xs">Unload Time</p>
|
||||
<p class="font-semibold mt-1">${expiresAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,7 +139,7 @@ class RunningModelsPage {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return date.toLocaleString("it-IT", {
|
||||
return date.toLocaleString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
(() => {
|
||||
if (!("serviceWorker" in navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
try {
|
||||
await navigator.serviceWorker.register("/service-worker.js", { scope: "/" });
|
||||
} catch (error) {
|
||||
console.error("Service worker registration failed:", error);
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,72 @@
|
||||
const CACHE_NAME = "llm-monitor-v1";
|
||||
const APP_SHELL = [
|
||||
"/",
|
||||
"/models-running",
|
||||
"/models-available",
|
||||
"/static/css/output.css",
|
||||
"/static/js/app.js",
|
||||
"/static/js/models-running.js",
|
||||
"/static/js/data-sync.worker.js",
|
||||
"/static/js/pwa-register.js",
|
||||
"/manifest.webmanifest",
|
||||
"/favicon.ico"
|
||||
];
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL))
|
||||
);
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
caches.keys().then((keys) =>
|
||||
Promise.all(
|
||||
keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))
|
||||
)
|
||||
)
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.method !== "GET") {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestUrl = new URL(event.request.url);
|
||||
const isApiRequest = requestUrl.pathname.startsWith("/api/");
|
||||
|
||||
if (isApiRequest) {
|
||||
event.respondWith(
|
||||
fetch(event.request).catch(() =>
|
||||
new Response(JSON.stringify({ detail: "Offline" }), {
|
||||
status: 503,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
})
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(
|
||||
caches.match(event.request).then((cached) => {
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
return fetch(event.request)
|
||||
.then((response) => {
|
||||
if (!response || response.status !== 200 || response.type !== "basic") {
|
||||
return response;
|
||||
}
|
||||
|
||||
const responseClone = response.clone();
|
||||
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, responseClone));
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match("/models-running"));
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "LLM Monitor",
|
||||
"short_name": "LLM Monitor",
|
||||
"description": "Monitor available and running Ollama models.",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#111827",
|
||||
"theme_color": "#111827",
|
||||
"lang": "en",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon.ico",
|
||||
"sizes": "any",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user