Add dedicated page for running Ollama models
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
class RunningModelsPage {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
document.getElementById("refresh-btn")?.addEventListener("click", () => {
|
||||
this.loadRunningModels();
|
||||
});
|
||||
|
||||
this.loadRunningModels();
|
||||
}
|
||||
|
||||
async loadRunningModels() {
|
||||
const container = document.getElementById("running-models");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
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>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/v1/models/running");
|
||||
if (!response.ok) {
|
||||
throw new Error("Errore nel caricamento dei modelli residenti");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const models = data.models || [];
|
||||
|
||||
this.renderStats(models);
|
||||
this.renderRunningModels(models);
|
||||
} catch (error) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-8 text-red-400">
|
||||
<p>Errore nel caricamento di ollama ps</p>
|
||||
</div>
|
||||
`;
|
||||
this.renderStats([]);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
renderStats(models) {
|
||||
const runningCountEl = document.getElementById("running-count");
|
||||
const vramTotalEl = document.getElementById("vram-total");
|
||||
const lastRefreshEl = document.getElementById("last-refresh");
|
||||
|
||||
const totalVram = models.reduce((sum, model) => sum + (model.size_vram || 0), 0);
|
||||
|
||||
if (runningCountEl) {
|
||||
runningCountEl.textContent = String(models.length);
|
||||
}
|
||||
if (vramTotalEl) {
|
||||
vramTotalEl.textContent = this.formatBytes(totalVram);
|
||||
}
|
||||
if (lastRefreshEl) {
|
||||
lastRefreshEl.textContent = new Date().toLocaleString("it-IT");
|
||||
}
|
||||
}
|
||||
|
||||
renderRunningModels(models) {
|
||||
const container = document.getElementById("running-models");
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (models.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-8 text-gray-400">
|
||||
<p>Nessun modello residente in memoria al momento.</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = models
|
||||
.map((model) => this.renderModelCard(model))
|
||||
.join("");
|
||||
}
|
||||
|
||||
renderModelCard(model) {
|
||||
const name = this.escapeHtml(model.name || "unknown");
|
||||
const modelId = this.escapeHtml(model.model || "-");
|
||||
const size = this.formatBytes(model.size || 0);
|
||||
const sizeVram = this.formatBytes(model.size_vram || 0);
|
||||
const processor = this.escapeHtml(model.details?.processor || "-");
|
||||
const expiresAt = model.expires_at ? this.formatDateTime(model.expires_at) : "-";
|
||||
|
||||
return `
|
||||
<div class="bg-gray-700 rounded-lg p-4 border border-gray-600">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<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>
|
||||
</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="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="font-semibold mt-1">${sizeVram}</p>
|
||||
</div>
|
||||
<div class="bg-gray-800 rounded p-3">
|
||||
<p class="text-gray-400 text-xs">Processor</p>
|
||||
<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="font-semibold mt-1">${expiresAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
formatBytes(bytes) {
|
||||
if (!bytes || bytes <= 0) {
|
||||
return "0 B";
|
||||
}
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
||||
const value = bytes / Math.pow(1024, index);
|
||||
return `${value.toFixed(2)} ${units[index]}`;
|
||||
}
|
||||
|
||||
formatDateTime(isoDate) {
|
||||
const date = new Date(isoDate);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return date.toLocaleString("it-IT", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
});
|
||||
}
|
||||
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = String(text);
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.runningModelsPage = new RunningModelsPage();
|
||||
});
|
||||
Reference in New Issue
Block a user