feat: add disk telemetry and clean navigation

This commit is contained in:
Hermes Agent
2026-08-26 00:43:48 +10:00
parent 57c8b841f2
commit a79f033c54
6 changed files with 58 additions and 8 deletions
+6 -1
View File
@@ -47,7 +47,12 @@ The Available downloads controls support:
Popularity and date ordering use upstream metadata only; the plugin does not invent popularity, dates, RAM requirements, or token metrics.
## MoE and activated-parameter metadata
## Disk telemetry and navigation layout
The runtime panel and top navigation now show live disk usage for the filesystem visible to the dashboard process: used percentage, used bytes, and free bytes. This is intentionally scoped to the dashboard-visible filesystem; if Ollama runs in a separate container or host, its model-volume disk usage may not be the same filesystem.
The view navigation was refactored into a stable four-view row for Ollama Chat, Installed, Top 20 popular, and Available downloads. Search and catalog filters now live in a separate aligned browse row. Catalog filters use a responsive grid and collapse cleanly on smaller screens, preventing the previous Available downloads misalignment.
Catalog model metadata is researched from each public Ollama family page, including the page description and family content, rather than inferred only from a model name. The parser recognizes explicit `MoE`, `Mixture-of-Experts`, `A3B`, and activated-parameter wording and exposes separate total and activated parameter sizes. For example: Laguna XS 2.1 is shown as 33B total / 3B activated; Ornith-1.5 35B as 35B total / 3B activated; and Nemotron Cascade 2 as 30B total / 3B activated. Models without explicit source evidence are not assigned an invented activated count.
+25 -3
View File
@@ -167,7 +167,7 @@
function RuntimePanel(props) {
var runtime = props.runtime || {}, total = Number(runtime.memory_total_bytes || 0), used = Number(runtime.memory_used_bytes || 0), pct = total ? Math.min(100, used * 100 / total) : 0;
var gpu = runtime.gpu || {}, cpu = runtime.cpu || {}, models = runtime.model_memory || [], loading = runtime.model_loading || [], cores = cpu.cores || [], gpus = gpu.gpus || [];
var gpu = runtime.gpu || {}, cpu = runtime.cpu || {}, disk = runtime.disk || {}, models = runtime.model_memory || [], loading = runtime.model_loading || [], cores = cpu.cores || [], gpus = gpu.gpus || [];
var ollamaBytes = Number(runtime.ollama_model_bytes || 0), ollamaTargetBytes = Number(runtime.ollama_target_model_bytes || ollamaBytes), ollamaPct = total ? Math.min(100, ollamaTargetBytes * 100 / total) : 0;
function percent(value) { return value == null ? "n/a" : Number(value).toFixed(1) + "%"; }
function meter(value) { return value == null ? 0 : Math.max(0, Math.min(100, Number(value))); }
@@ -176,7 +176,7 @@
h("div", { className: "ollama-runtime-grid" },
h("div", { className: "ollama-runtime-stat" }, h("small", null, "System RAM used"), h("strong", null, fmtBytes(used), " / ", fmtBytes(total)), h("div", { className: "ollama-meter" }, h("span", { style: { width: pct + "%" } })), h("small", null, fmtBytes(runtime.memory_available_bytes || 0), " available")),
h("div", { className: "ollama-runtime-stat ollama-cpu-stat" }, h("small", null, "CPU usage"), h("strong", null, percent(cpu.usage_percent)), h("div", { className: "ollama-meter" }, h("span", { style: { width: meter(cpu.usage_percent) + "%" } })), h("small", null, cpu.count ? cpu.count + " logical CPUs · load " + (cpu.load_average || []).map(function (value) { return Number(value).toFixed(2); }).join(" / ") : "Unavailable")),
h("div", { className: "ollama-runtime-stat" }, h("small", null, "Swap used"), h("strong", null, fmtBytes(runtime.swap_used_bytes || 0), " / ", fmtBytes(runtime.swap_total_bytes || 0)), h("small", null, "Host-wide live statistic")),
h("div", { className: "ollama-runtime-stat ollama-disk-stat" }, h("small", null, "Disk usage"), h("strong", null, percent(disk.used_percent)), h("div", { className: "ollama-meter" }, h("span", { style: { width: meter(disk.used_percent) + "%" } })), h("small", null, disk.available ? fmtBytes(disk.used_bytes) + " used · " + fmtBytes(disk.free_bytes) + " free" : "Unavailable")),
h("div", { className: "ollama-runtime-stat ollama-gpu-stat" }, h("small", null, "GPU usage"), h("strong", null, percent(gpu.utilization_percent), " · ", gpu.count || 0, " GPU", (gpu.count || 0) === 1 ? "" : "s"), h("div", { className: "ollama-meter" }, h("span", { style: { width: meter(gpu.utilization_percent) + "%" } })), h("small", null, gpu.telemetry_available && gpus.length ? gpus.map(function (item) { return item.name + " · " + fmtBytes(item.used_bytes) + " / " + fmtBytes(item.total_bytes); }).join("; ") : "Unavailable")),
h("div", { className: "ollama-runtime-stat ollama-weight-stat" }, h("small", null, "Ollama model weights"), h("strong", null, fmtBytes(ollamaBytes), " resident"), h("div", { className: "ollama-meter" }, h("span", { style: { width: ollamaPct + "%" } })), h("small", null, loading.length ? "Loading target: " + fmtBytes(ollamaTargetBytes) : "Mapped weight bytes; Linux may report them as file cache"))
),
@@ -456,10 +456,32 @@
var needle = query.toLowerCase().trim(); if (needle) models = models.filter(function (model) { return (model.name + " " + model.family + " " + (model.strengths || []).join(" ") + " " + (model.capabilities || []).join(" ")).toLowerCase().indexOf(needle) >= 0; });
var catalogCapabilities = data && data.catalog_filter_options ? data.catalog_filter_options.capabilities || [] : [];
var jobs = data && data.jobs ? data.jobs.filter(function (job) { return job.state === "running"; }) : [];
var disk = data && data.disk ? data.disk : {};
var navTabs = h("nav", { className: "ollama-tabs", "aria-label": "Ollama views" },
h(Button, { className: tab === "chat" ? "selected" : "", onClick: function () { setTab("chat"); } }, "Ollama Chat"),
h(Button, { className: tab === "installed" ? "selected" : "", onClick: function () { setTab("installed"); } }, "Installed (" + ((data && data.models) || []).length + ")"),
h(Button, { className: tab === "popular" ? "selected" : "", onClick: function () { setTab("popular"); } }, "Top 20 popular (" + ((data && data.popular) || []).length + ")"),
h(Button, { className: tab === "catalog" ? "selected" : "", onClick: function () { setTab("catalog"); } }, "Available downloads (" + ((data && data.catalog) || []).length + ")")
);
var catalogControls = tab === "catalog" && h("div", { className: "ollama-catalog-controls" },
h("label", null, "Type", h("select", { className: "ollama-catalog-select", value: catalogType, onChange: function (event) { setCatalogType(event.target.value); } },
h("option", { value: "all" }, "All types"), h("option", { value: "moe" }, "MoE only"), h("option", { value: "dense" }, "Dense only")
)),
h("label", null, "Ability", h("select", { className: "ollama-catalog-select", value: catalogCapability, onChange: function (event) { setCatalogCapability(event.target.value); } },
h("option", { value: "all" }, "All abilities"), catalogCapabilities.map(function (capability) { return h("option", { key: capability, value: capability }, capability); })
)),
h("label", null, "Organize", h("select", { className: "ollama-catalog-select", value: catalogSort, onChange: function (event) { setCatalogSort(event.target.value); } },
h("option", { value: "popularity" }, "Popularity"), h("option", { value: "newest" }, "Newest"), h("option", { value: "size_asc" }, "Size: smallest first"), h("option", { value: "size_desc" }, "Size: largest first"), h("option", { value: "name" }, "Name")
))
);
var browseToolbar = tab !== "chat" && h("div", { className: "ollama-browse-row" },
h("input", { className: "ollama-search", value: query, placeholder: "Search models, capabilities, or strengths…", onChange: function (event) { setQuery(event.target.value); } }),
catalogControls
);
return h("main", { className: "ollama-page" }, h("header", { className: "ollama-hero" }, h("div", null, h("div", { className: "ollama-eyebrow" }, "LOCAL MODEL OPERATIONS"), h("h1", null, "Ollama Models"), h("p", null, "Inspect, chat with, download, update, and remove models from the local Ollama runtime.")), h("div", { className: "ollama-health" }, h(Badge, { tone: data && data.ollama && data.ollama.available ? "live" : "danger" }, data && data.ollama && data.ollama.available ? "Ollama online" : "Ollama unavailable"), data && data.ollama && h("span", null, "v" + (data.ollama.version || "unknown")), h(Button, { disabled: busy === "catalog", onClick: refreshCatalog }, busy === "catalog" ? "Refreshing…" : "Refresh catalog")), h(ConnectionPanel, { data: data, reload: load })),
targetDialog && h("div", { className: "ollama-target-modal" }, h("div", { className: "ollama-target-card" }, h("h3", null, "Where should " + targetDialog.name + " be downloaded?"), h("p", null, "Both local and remote Ollama instances are online. Choose the destination for this model."), targetDialog.targets.map(function (item) { return h(Button, { key: item.kind, onClick: function () { var chosen = targetDialog; setTargetDialog(null); action(chosen.kind, chosen.name, item.kind); } }, (item.kind || "local").toUpperCase(), " · ", item.url, " · v", item.version, " · ", item.models, " models"); }), h(Button, { className: "secondary", onClick: function () { setTargetDialog(null); } }, "Cancel"))),
notice && h("div", { className: "ollama-notice " + (notice.error ? "error" : notice.warning ? "warning" : "ok") }, notice.error || notice.warning || notice.ok),
h("section", { className: "ollama-toolbar" }, h("div", { className: "ollama-tabs" }, h(Button, { className: tab === "chat" ? "selected" : "", onClick: function () { setTab("chat"); } }, "Ollama Chat"), h(Button, { className: tab === "installed" ? "selected" : "", onClick: function () { setTab("installed"); } }, "Installed (" + ((data && data.models) || []).length + ")"), h(Button, { className: tab === "popular" ? "selected" : "", onClick: function () { setTab("popular"); } }, "Top 20 popular (" + ((data && data.popular) || []).length + ")"), h(Button, { className: tab === "catalog" ? "selected" : "", onClick: function () { setTab("catalog"); } }, "Available downloads (" + ((data && data.catalog) || []).length + ")")), tab !== "chat" && h("input", { className: "ollama-search", value: query, placeholder: "Search models, capabilities, or strengths…", onChange: function (event) { setQuery(event.target.value); } }), tab === "catalog" && h("div", { className: "ollama-catalog-controls" }, h("label", null, "Type", h("select", { className: "ollama-catalog-select", value: catalogType, onChange: function (event) { setCatalogType(event.target.value); } }, h("option", { value: "all" }, "All types"), h("option", { value: "moe" }, "MoE only"), h("option", { value: "dense" }, "Dense only"))), h("label", null, "Ability", h("select", { className: "ollama-catalog-select", value: catalogCapability, onChange: function (event) { setCatalogCapability(event.target.value); } }, h("option", { value: "all" }, "All abilities"), catalogCapabilities.map(function (capability) { return h("option", { key: capability, value: capability }, capability); }))), h("label", null, "Organize", h("select", { className: "ollama-catalog-select", value: catalogSort, onChange: function (event) { setCatalogSort(event.target.value); } }, h("option", { value: "popularity" }, "Popularity"), h("option", { value: "newest" }, "Newest"), h("option", { value: "size_asc" }, "Size: smallest first"), h("option", { value: "size_desc" }, "Size: largest first"), h("option", { value: "name" }, "Name"))))),
h("section", { className: "ollama-toolbar" }, h("div", { className: "ollama-nav-row" }, navTabs, h("div", { className: "ollama-toolbar-disk" }, h("span", null, "Disk"), h("strong", null, disk.used_percent == null ? "n/a" : Number(disk.used_percent).toFixed(1) + "%"), h("small", null, disk.available ? fmtBytes(disk.free_bytes) + " free" : "Unavailable"))), browseToolbar),
tab !== "chat" && h("div", { className: "ollama-info-strip" }, h("span", null, data && data.models ? data.models.filter(function (m) { return m.loaded; }).length + " currently loaded" : "Loading runtime state…"), h("span", null, "Catalog checked " + (data && data.catalog_updated_at ? fmtDate(data.catalog_updated_at) : "not yet")), h("span", null, "Next daily check " + (data && data.next_catalog_refresh ? fmtDate(data.next_catalog_refresh) : "01:00 Melbourne time") + " (1:00 AM Melbourne time)")),
tab === "chat" && h(ChatPanel, { models: data && data.models ? data.models : [], refresh: load }),
tab === "popular" && h("p", { className: "ollama-popular-note" }, "Popular is limited to models with known size and RAM estimates at or below the detected system RAM (" + (data && data.popular_filter && data.popular_filter.max_expected_ram_gib ? data.popular_filter.max_expected_ram_gib + " GiB" : "detecting…") + "). Oversized families are represented by a smaller fitting variant when available."), jobs.length > 0 && h("section", { className: "ollama-jobs" }, jobs.map(function (job) { return h("div", { key: job.id }, h("strong", null, job.action + " · " + job.name + " · " + (job.target || "local") + (job.endpoint ? " · " + job.endpoint : "")), h("span", null, job.percent == null ? job.status : job.percent + "%")); })),
+4 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,7 +3,7 @@
"label": "Ollama Models",
"description": "Inspect, manage, and chat with local Ollama models, including shared persistent conversations, performance metrics, images, PDFs, URLs, and live memory telemetry.",
"icon": "Cpu",
"version": "1.5.19",
"version": "1.5.20",
"tab": {"path": "/ollama-manager", "position": "after:models"},
"entry": "dist/index.js",
"css": "dist/style.css",
+21
View File
@@ -10,6 +10,7 @@ import json
import mimetypes
import os
import re
import shutil
import socket
import sqlite3
import subprocess
@@ -576,6 +577,24 @@ def _gpu_snapshot() -> dict[str, Any]:
return {"detected": nvidia_present, "telemetry_available": False, "count": 0, "utilization_percent": None, "gpus": []}
def _disk_snapshot() -> dict[str, Any]:
"""Return usage for the filesystem visible to the dashboard process."""
path = "/"
try:
usage = shutil.disk_usage(path)
except OSError:
return {"path": path, "available": False, "total_bytes": 0, "used_bytes": 0, "free_bytes": 0, "used_percent": None}
return {
"path": path,
"available": True,
"total_bytes": usage.total,
"used_bytes": usage.used,
"free_bytes": usage.free,
"used_percent": round(usage.used * 100 / usage.total, 1) if usage.total else None,
"scope": "Filesystem visible to the dashboard process",
}
def _runtime_snapshot() -> dict[str, Any]:
mem = _read_meminfo()
total = mem.get("MemTotal", 0)
@@ -633,6 +652,7 @@ def _runtime_snapshot() -> dict[str, Any]:
"ollama_model_bytes": ollama_model_bytes,
"ollama_model_vram_bytes": ollama_model_vram_bytes,
"ollama_target_model_bytes": ollama_target_model_bytes,
"disk": _disk_snapshot(),
"cpu": cpu,
"gpu": gpu,
}
@@ -1898,6 +1918,7 @@ def status() -> dict[str, Any]:
),
},
"connections": connection_rows,
"disk": _disk_snapshot(),
"models": local,
"popular": popular,
"popular_filter": {
+1 -1
View File
@@ -1,5 +1,5 @@
name: ollama-manager
version: 1.5.19
version: 1.5.20
description: Native dashboard manager and chat interface for local Ollama models, attachments, URLs, shared persistent conversations, performance metrics, and live runtime telemetry.
auto_install_dependencies: true
python_dependencies: