diff --git a/README.md b/README.md index bb90515..d772487 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,10 @@ environment: The dashboard plugin API is mounted when the dashboard starts. Restart Hermes after installing or enabling the plugin; otherwise the plugin page can load while `/api/plugins/ollama-manager/status` still returns 404. +## Download storage + +The plugin does not store model blobs in Hermes. It sends Ollama's native `POST /api/pull` request to the selected endpoint. Therefore a download goes to the Ollama instance shown in the job message, and the Ollama service owns the model storage location. The exact path is controlled by Ollama's `OLLAMA_MODELS` setting; common Linux service/user locations are `/usr/share/ollama/.ollama/models` and `~/.ollama/models`. Check the Ollama service environment on the target host to determine the authoritative path. + ## Security limits - Uploaded files are limited to 20 MiB each diff --git a/dashboard/dist/index.js b/dashboard/dist/index.js index cc7ffe8..44fb68f 100644 --- a/dashboard/dist/index.js +++ b/dashboard/dist/index.js @@ -407,7 +407,7 @@ 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"))))), 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), h("span", null, job.percent == null ? job.status : job.percent + "%")); })), + 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 + "%")); })), tab !== "chat" && loading && h(Empty, null, "Loading local Ollama inventory…"), tab !== "chat" && !loading && !models.length && h(Empty, null, tab === "installed" ? "No local models found." : tab === "popular" ? "No popular catalog entries available." : "No catalog entries available. Try Refresh catalog."), tab !== "chat" && h("section", { className: "ollama-grid" }, models.map(function (model) { return h(ModelCard, { key: model.name, model: model, installed: tab === "installed" || !!model.installed, busy: busy, action: action }); })) ); } diff --git a/dashboard/manifest.json b/dashboard/manifest.json index 4237c43..200f993 100644 --- a/dashboard/manifest.json +++ b/dashboard/manifest.json @@ -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.6", + "version": "1.5.7", "tab": {"path": "/ollama-manager", "position": "after:models"}, "entry": "dist/index.js", "css": "dist/style.css", diff --git a/dashboard/plugin_api.py b/dashboard/plugin_api.py index 37140ef..15e3753 100644 --- a/dashboard/plugin_api.py +++ b/dashboard/plugin_api.py @@ -1632,24 +1632,27 @@ def catalog_refresh() -> dict[str, Any]: def pull_model(body: ModelRequest) -> dict[str, Any]: name = _valid_name(body.name) target = str(body.target or "local").strip().lower() - _target_endpoint(target) - return {"ok": True, "job_id": _new_job(name, "download", target), "message": f"Downloading or updating {name} on {target}"} + endpoint = _target_endpoint(target) + job_id = _new_job(name, "download", target) + return {"ok": True, "job_id": job_id, "endpoint": endpoint, "target": target, "message": f"Downloading or updating {name} on {target} ({endpoint})"} @router.post("/redownload") def redownload_model(body: ModelRequest) -> dict[str, Any]: name = _valid_name(body.name) target = str(body.target or "local").strip().lower() - _target_endpoint(target) - return {"ok": True, "job_id": _new_job(name, "redownload", target), "message": f"Re-downloading or updating {name} on {target}"} + endpoint = _target_endpoint(target) + job_id = _new_job(name, "redownload", target) + return {"ok": True, "job_id": job_id, "endpoint": endpoint, "target": target, "message": f"Re-downloading or updating {name} on {target} ({endpoint})"} @router.delete("/model") def delete_model(body: ModelRequest) -> dict[str, Any]: name = _valid_name(body.name) target = str(body.target or "local").strip().lower() - _target_endpoint(target) - return {"ok": True, "job_id": _new_job(name, "delete", target), "message": f"Removing {name} from {target}"} + endpoint = _target_endpoint(target) + job_id = _new_job(name, "delete", target) + return {"ok": True, "job_id": job_id, "endpoint": endpoint, "target": target, "message": f"Removing {name} from {target} ({endpoint})"} def create_ollama_routes(app) -> None: diff --git a/plugin.yaml b/plugin.yaml index e81d0c6..28f6012 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,5 +1,5 @@ name: ollama-manager -version: 1.5.6 +version: 1.5.7 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: