feat: remove saved Ollama connection configs

This commit is contained in:
Hermes Agent
2026-08-24 22:35:42 +10:00
parent a9e8c63791
commit f45fbf0b1b
6 changed files with 34 additions and 9 deletions
+7 -1
View File
@@ -68,13 +68,19 @@
setBusy("save"); setResult(null);
fetchJSON(API + "/connections/configure", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: url.trim(), role: role }) }).then(function (value) { setResult({ ok: true, value: value }); if (props.reload) props.reload(); }).catch(function (err) { setResult({ error: err.message || String(err) }); }).finally(function () { setBusy(""); });
}
function removeSaved(item) {
if (!item || !item.saved || !item.kind) return;
if (!window.confirm("Remove the saved " + item.kind + " Ollama configuration?")) return;
setBusy("remove:" + item.kind); setResult(null);
fetchJSON(API + "/connections/" + encodeURIComponent(item.kind), { method: "DELETE" }).then(function (value) { setResult({ ok: true, value: value }); if (props.reload) props.reload(); }).catch(function (err) { setResult({ error: err.message || String(err) }); }).finally(function () { setBusy(""); });
}
function useDetected(item) { setUrl(item.url || ""); setRole(item.kind || "local"); }
var rows = data.connections || [];
return h("section", { className: "ollama-connection-panel" },
h("div", { className: "ollama-connection-heading" }, h("div", null, h("strong", null, "Ollama connection"), h("small", null, ollama.containerized ? "Docker runtime · local and remote endpoints supported" : "Physical runtime · local and remote endpoints supported"))),
h("div", { className: "ollama-connection-form" }, h("select", { className: "ollama-connection-role", value: role, onChange: function (event) { setRole(event.target.value); } }, h("option", { value: "local" }, "Local"), h("option", { value: "remote" }, "Remote")), h("input", { className: "ollama-connection-input", value: url, placeholder: "http://host-or-container:11434", onChange: function (event) { setUrl(event.target.value); } }), h(Button, { onClick: test, disabled: !url.trim() || !!busy }, busy === "test" ? "Testing…" : "Test"), h(Button, { onClick: save, disabled: !url.trim() || !!busy }, busy === "save" ? "Saving…" : "Save")),
result && h("div", { className: "ollama-connection-result " + (result.error ? "error" : "ok") }, result.error || ((result.value && result.value.version) ? "Online · v" + result.value.version + " · " + (result.value.models || 0) + " models" : "Connection accepted")),
h("div", { className: "ollama-connection-list" }, rows.length ? rows.map(function (item) { return h("button", { type: "button", className: "ollama-connection-row", key: item.kind + ":" + item.url, onClick: function () { useDetected(item); } }, h("span", { className: "ollama-connection-dot " + (item.available ? "online" : "offline") }), h("span", null, h("strong", null, (item.kind || "local").toUpperCase(), " · ", item.label || item.url), h("small", null, item.url, item.available ? " · v" + item.version + " · " + item.models + " models" + (item.model_names && item.model_names.length ? " · " + item.model_names.join(", ") : "") + (item.same_endpoint ? " · same endpoint as local" : "") : " · unavailable"))); }) : h("small", null, "No endpoints detected yet. Enter an Ollama URL and test it."))
h("div", { className: "ollama-connection-list" }, rows.length ? rows.map(function (item) { return h("div", { className: "ollama-connection-row", key: item.kind + ":" + item.url }, h("button", { type: "button", className: "ollama-connection-row-main", onClick: function () { useDetected(item); } }, h("span", { className: "ollama-connection-dot " + (item.available ? "online" : "offline") }), h("span", null, h("strong", null, (item.kind || "local").toUpperCase(), " · ", item.label || item.url), h("small", null, item.url, item.available ? " · v" + item.version + " · " + item.models + " models" + (item.model_names && item.model_names.length ? " · " + item.model_names.join(", ") : "") + (item.same_endpoint ? " · same endpoint as local" : "") : " · unavailable"))), item.saved && h(Button, { className: "ollama-connection-remove", disabled: !!busy, onClick: function () { removeSaved(item); } }, busy === "remove:" + item.kind ? "Removing…" : "Remove saved")); }) : h("small", null, "No endpoints detected yet. Enter an Ollama URL and test it."))
);
}
+2 -1
View File
@@ -5,6 +5,7 @@
.ollama-chat{margin-top:18px}.ollama-chat-header{display:flex;justify-content:space-between;gap:20px;align-items:flex-start;padding:18px;border:1px solid rgba(164,211,199,.16);border-radius:12px;background:rgba(23,52,48,.42)}.ollama-chat-header h2{margin:6px 0;color:#effcf8}.ollama-chat-header p{margin:0;color:#a5bfba;font-size:12px;max-width:720px}.ollama-chat-model{display:flex;align-items:flex-end;gap:10px;min-width:260px}.ollama-chat-model label{display:flex;flex-direction:column;gap:6px;color:#a5bfba;font-size:11px}.ollama-chat-model select,.ollama-url-input{border:1px solid rgba(155,205,194,.28);border-radius:7px;background:#102d29;color:#e8f2ef;padding:9px;font:inherit;font-size:12px}.ollama-runtime-panel{margin-top:14px;padding:16px;border:1px solid rgba(164,211,199,.16);border-radius:12px;background:rgba(10,31,28,.7)}.ollama-runtime-heading{display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.ollama-runtime-heading h3{margin:0 0 3px}.ollama-runtime-heading p{margin:0;color:#8fa9a4;font-size:11px}.ollama-badge.muted{color:#a7b7b4;border-color:rgba(167,183,180,.25)}.ollama-runtime-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:14px}.ollama-runtime-stat{display:flex;flex-direction:column;gap:5px;min-height:70px;padding:11px;border-radius:8px;background:rgba(71,117,108,.11);color:#a5bfba}.ollama-runtime-stat strong{color:#effcf8;font-size:15px}.ollama-runtime-stat small{font-size:10px}.ollama-meter{height:5px;border-radius:99px;background:#173c36;overflow:hidden}.ollama-meter span{display:block;height:100%;border-radius:inherit;background:#75d2b7}.ollama-memory-chart{height:70px;display:flex;align-items:flex-end;gap:2px;margin-top:14px;padding:5px 0;border-bottom:1px solid rgba(164,211,199,.18)}.ollama-memory-chart span{flex:1;min-width:2px;background:#55a995;border-radius:2px 2px 0 0;opacity:.8}.ollama-loaded-memory h4{margin:15px 0 8px}.ollama-loaded-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:9px 0;border-top:1px solid rgba(164,211,199,.1);font-size:11px;color:#a5bfba}.ollama-loaded-row strong{color:#effcf8;min-width:180px}.ollama-chat-layout{display:flex;flex-direction:column;gap:14px;margin-top:14px}.ollama-conversation{min-height:360px;max-height:620px;overflow:auto;order:1}.ollama-composer{order:2}.ollama-conversation{min-height:320px;max-height:620px;overflow:auto;padding:14px;border:1px solid rgba(164,211,199,.16);border-radius:12px;background:rgba(10,25,23,.62)}.ollama-message{margin:0 0 14px;padding:11px 13px;border-radius:9px;white-space:pre-wrap;line-height:1.5;font-size:13px}.ollama-message small{display:block;margin-bottom:5px;color:#8fc7ba;font-size:10px;text-transform:uppercase;letter-spacing:.1em}.ollama-message.user{margin-left:12%;background:rgba(54,105,94,.3)}.ollama-message.assistant{margin-right:8%;background:rgba(34,63,62,.6)}.ollama-composer{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px solid rgba(164,211,199,.16);border-radius:12px;background:rgba(23,52,48,.42)}.ollama-composer textarea{min-height:160px;resize:vertical;border:1px solid rgba(155,205,194,.25);border-radius:8px;background:#0d2522;color:#e8f2ef;padding:11px;font:inherit;font-size:13px}.ollama-attachment-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.ollama-file-button{display:inline-flex;align-items:center;border:1px solid rgba(155,205,194,.24);border-radius:7px;background:rgba(70,117,108,.18);color:#dbebe7;padding:8px 11px;cursor:pointer;font-size:11px}.ollama-file-button input{display:none}.ollama-url-input{flex:1;min-width:160px}.ollama-attachments{display:flex;gap:6px;flex-wrap:wrap}.ollama-attachment{display:inline-flex;align-items:center;gap:5px;padding:5px 8px;border-radius:999px;background:rgba(80,125,115,.16);color:#c5dfd8;font-size:10px;max-width:100%;overflow:hidden;text-overflow:ellipsis}.ollama-attachment button{border:0;background:none;color:#ffb3b3;cursor:pointer}.ollama-chat-footnote{margin:0;color:#819b96;font-size:10px;line-height:1.45}
@media(max-width:900px){.ollama-chat-header,.ollama-chat-layout{display:block}.ollama-chat-model{margin-top:15px;align-items:center}.ollama-composer{margin-top:14px}.ollama-runtime-grid{grid-template-columns:1fr 1fr}}
.ollama-persistence-panel{display:grid;gap:12px;margin:16px 0;padding:16px;border:1px solid rgba(164,211,199,.16);border-radius:12px;background:rgba(10,31,28,.7)}.ollama-persistence-heading{display:flex;justify-content:space-between;gap:16px;align-items:center}.ollama-persistence-heading h3{margin:0}.ollama-persistence-heading p{margin:4px 0 0;color:#8fa9a4;font-size:11px}.ollama-conversation-list{display:flex;flex-wrap:wrap;gap:8px}.ollama-conversation-list .ollama-button{font-size:11px}.ollama-conversation-list .selected{background:#3e8073;border-color:#8dd2c1}.ollama-metrics-summary,.ollama-metrics-detail{display:flex;flex-wrap:wrap;gap:12px;font-size:11px;color:#a5bfba}.ollama-metrics-summary strong{color:#effcf8}.ollama-metrics-detail{padding-top:8px;border-top:1px solid rgba(164,211,199,.14)}
@media(max-width:600px){.ollama-runtime-grid{grid-template-columns:1fr}.ollama-chat-model{display:block}.ollama-chat-model select{width:100%;margin-bottom:8px}.ollama-message.user{margin-left:0}.ollama-message.assistant{margin-right:0}}.ollama-connection-panel{min-width:340px;max-width:620px;margin-top:14px;padding:12px;border:1px solid rgba(164,211,199,.2);border-radius:10px;background:rgba(10,31,28,.72);box-shadow:0 8px 24px rgba(0,0,0,.12)}.ollama-connection-heading{display:flex;justify-content:space-between;gap:10px;color:#d7ebe5}.ollama-connection-heading strong{font-size:12px}.ollama-connection-heading small{display:block;margin-top:3px;color:#8fa9a4;font-size:10px}.ollama-connection-form{display:flex;gap:6px;align-items:center;margin-top:9px}.ollama-connection-role,.ollama-connection-input{border:1px solid rgba(155,205,194,.28);border-radius:6px;background:#102d29;color:#e8f2ef;padding:7px;font:inherit;font-size:11px}.ollama-connection-input{min-width:190px;flex:1}.ollama-connection-result{margin-top:7px;font-size:10px}.ollama-connection-result.ok{color:#9af1c7}.ollama-connection-result.error{color:#ffb1b1}.ollama-connection-list{display:grid;gap:5px;margin-top:8px}.ollama-connection-row{display:flex;align-items:center;gap:7px;width:100%;border:0;border-top:1px solid rgba(164,211,199,.1);padding:7px 0;background:none;color:#c5ddd7;text-align:left;cursor:pointer;font:inherit}.ollama-connection-row span:nth-child(2){display:flex;flex-direction:column;gap:2px;min-width:0}.ollama-connection-row strong{font-size:10px}.ollama-connection-row small{color:#8fa9a4;font-size:9px;overflow-wrap:anywhere}.ollama-connection-dot{width:7px;height:7px;border-radius:50%;background:#b36d6d;flex:0 0 auto}.ollama-connection-dot.online{background:#75d2b7;box-shadow:0 0 8px rgba(117,210,183,.55)}.ollama-target-modal{position:fixed;inset:0;z-index:20;display:flex;align-items:center;justify-content:center;padding:20px;background:rgba(4,15,14,.72)}.ollama-target-card{display:grid;gap:10px;max-width:560px;width:100%;padding:20px;border:1px solid rgba(141,210,193,.38);border-radius:12px;background:#102d29;box-shadow:0 14px 50px rgba(0,0,0,.35)}.ollama-target-card h3{margin:0;color:#effcf8}.ollama-target-card p{margin:0;color:#a5bfba;font-size:12px}.ollama-target-card .ollama-button{text-align:left}@media(max-width:900px){.ollama-connection-panel{min-width:0;max-width:none}.ollama-connection-form{flex-wrap:wrap}.ollama-connection-input{min-width:160px}}
@media(max-width:600px){.ollama-runtime-grid{grid-template-columns:1fr}.ollama-chat-model{display:block}.ollama-chat-model select{width:100%;margin-bottom:8px}.ollama-message.user{margin-left:0}.ollama-message.assistant{margin-right:0}}.ollama-connection-panel{min-width:340px;max-width:620px;margin-top:14px;padding:12px;border:1px solid rgba(164,211,199,.2);border-radius:10px;background:rgba(10,31,28,.72);box-shadow:0 8px 24px rgba(0,0,0,.12)}.ollama-connection-heading{display:flex;justify-content:space-between;gap:10px;color:#d7ebe5}.ollama-connection-heading strong{font-size:12px}.ollama-connection-heading small{display:block;margin-top:3px;color:#8fa9a4;font-size:10px}.ollama-connection-form{display:flex;gap:6px;align-items:center;margin-top:9px}.ollama-connection-role,.ollama-connection-input{border:1px solid rgba(155,205,194,.28);border-radius:6px;background:#102d29;color:#e8f2ef;padding:7px;font:inherit;font-size:11px}.ollama-connection-input{min-width:190px;flex:1}.ollama-connection-result{margin-top:7px;font-size:10px}.ollama-connection-result.ok{color:#9af1c7}.ollama-connection-result.error{color:#ffb1b1}.ollama-connection-list{display:grid;gap:5px;margin-top:8px}.ollama-connection-row{display:flex;align-items:center;gap:7px;width:100%;border:0;border-top:1px solid rgba(164,211,199,.1);padding:7px 0;background:none;color:#c5ddd7;text-align:left;cursor:pointer;font:inherit}.ollama-connection-row span:nth-child(2){display:flex;flex-direction:column;gap:2px;min-width:0}.ollama-connection-row strong{font-size:10px}.ollama-connection-row small{color:#8fa9a4;font-size:9px;overflow-wrap:anywhere}.ollama-connection-dot{width:7px;height:7px;border-radius:50%;background:#b36d6d;flex:0 0 auto}.ollama-connection-dot.online{background:#75d2b7;box-shadow:0 0 8px rgba(117,210,183,.55)}.ollama-connection-row-main{display:flex;align-items:center;gap:7px;flex:1;min-width:0;border:0;padding:0;background:none;color:inherit;text-align:left;cursor:pointer;font:inherit}.ollama-connection-remove{flex:0 0 auto;padding:5px 7px;font-size:9px}.ollama-connection-row-main>span:nth-child(2){display:flex;flex-direction:column;gap:2px;min-width:0}.ollama-connection-row-main strong{font-size:10px}.ollama-connection-row-main small{color:#8fa9a4;font-size:9px;overflow-wrap:anywhere}
.ollama-target-modal{position:fixed;inset:0;z-index:20;display:flex;align-items:center;justify-content:center;padding:20px;background:rgba(4,15,14,.72)}.ollama-target-card{display:grid;gap:10px;max-width:560px;width:100%;padding:20px;border:1px solid rgba(141,210,193,.38);border-radius:12px;background:#102d29;box-shadow:0 14px 50px rgba(0,0,0,.35)}.ollama-target-card h3{margin:0;color:#effcf8}.ollama-target-card p{margin:0;color:#a5bfba;font-size:12px}.ollama-target-card .ollama-button{text-align:left}@media(max-width:900px){.ollama-connection-panel{min-width:0;max-width:none}.ollama-connection-form{flex-wrap:wrap}.ollama-connection-input{min-width:160px}}
.ollama-catalog-controls{display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end;margin-top:12px;padding:10px;border:1px solid rgba(164,211,199,.16);border-radius:10px;background:rgba(10,31,28,.55)}.ollama-catalog-controls label{display:flex;flex-direction:column;gap:5px;color:#a5bfba;font-size:10px;text-transform:uppercase;letter-spacing:.06em}.ollama-catalog-select{min-width:145px;border:1px solid rgba(155,205,194,.28);border-radius:7px;background:#102d29;color:#e8f2ef;padding:8px;font:inherit;font-size:11px;text-transform:none;letter-spacing:normal}
.ollama-thinking-status{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:14px;padding:14px 16px;border:1px solid rgba(117,210,183,.42);border-radius:10px;background:linear-gradient(90deg,rgba(46,111,96,.34),rgba(24,64,57,.5));box-shadow:0 0 20px rgba(74,190,158,.08)}.ollama-thinking-copy{flex:1;min-width:200px}.ollama-thinking-details{flex-basis:100%;padding:12px;border-top:1px solid rgba(164,211,199,.17);color:#a5bfba}.ollama-thinking-detail-grid{display:grid;grid-template-columns:repeat(5,minmax(100px,1fr));gap:8px;margin-bottom:8px}.ollama-thinking-detail-grid span{display:flex;flex-direction:column;gap:3px;padding:8px;border-radius:7px;background:rgba(71,117,108,.11);font-size:10px;color:#8fb5ac}.ollama-thinking-detail-grid strong{color:#e4f4ef;font-size:11px;overflow-wrap:anywhere}.ollama-thinking-details small{font-size:10px;color:#819b96}.ollama-thinking-status .thinking-stop{color:#ffb8b8;border-color:rgba(255,110,110,.45)}.ollama-composer.drop-active{border-color:rgba(117,210,183,.8);background:linear-gradient(135deg,rgba(33,92,79,.52),rgba(23,52,48,.62));box-shadow:0 0 24px rgba(117,210,183,.16)}.ollama-drop-hint{padding:9px;border:1px dashed rgba(117,210,183,.7);border-radius:7px;text-align:center;color:#b8ead9;font-size:11px;background:rgba(117,210,183,.08)}.ollama-thinking-spinner{display:flex;align-items:center;gap:4px;min-width:28px}.ollama-thinking-spinner span{width:7px;height:7px;border-radius:50%;background:#75d2b7;animation:ollama-thinking-pulse 1.1s ease-in-out infinite}.ollama-thinking-spinner span:nth-child(2){animation-delay:.18s}.ollama-thinking-spinner span:nth-child(3){animation-delay:.36s}.ollama-thinking-copy{display:flex;flex-direction:column;gap:3px}.ollama-thinking-copy strong{color:#effcf8;font-size:13px}.ollama-thinking-copy span{color:#b9d8d0;font-size:12px}.ollama-thinking-copy small{color:#8fb5ac;font-size:10px}@keyframes ollama-thinking-pulse{0%,80%,100%{opacity:.35;transform:scale(.8)}40%{opacity:1;transform:scale(1.2)}}
+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.8",
"version": "1.5.9",
"tab": {"path": "/ollama-manager", "position": "after:models"},
"entry": "dist/index.js",
"css": "dist/style.css",
+22 -4
View File
@@ -310,8 +310,7 @@ def _apply_saved_connection() -> None:
LOCAL_OLLAMA = saved
return
configured = os.environ.get("OLLAMA_HOST", "").strip()
if configured:
LOCAL_OLLAMA = configured.rstrip("/")
LOCAL_OLLAMA = configured.rstrip("/") if configured else _ollama_base_url()
def _target_endpoint(target: str = "local") -> str:
@@ -362,9 +361,9 @@ def _connection_snapshot() -> list[dict[str, Any]]:
seen.add(key)
try:
result = _probe_endpoint(normalized, timeout=3)
result.update({"kind": kind, "label": label})
result.update({"kind": kind, "label": label, "saved": saved.get(f"{kind}_url") == normalized})
except Exception as exc:
result = {"available": False, "kind": kind, "label": label, "url": normalized, "error": str(exc)[:240]}
result = {"available": False, "kind": kind, "label": label, "url": normalized, "saved": saved.get(f"{kind}_url") == normalized, "error": str(exc)[:240]}
results.append(result)
local_urls = {row.get("url") for row in results if row.get("kind") == "local" and row.get("available")}
for row in results:
@@ -1512,6 +1511,25 @@ def connections_configure(body: ConnectionRequest) -> dict[str, Any]:
return result
@router.delete("/connections/{role}")
def connections_delete(role: str) -> dict[str, Any]:
role = str(role or "").strip().lower()
if role not in {"local", "remote"}:
raise HTTPException(400, "Connection role must be local or remote")
saved = _read_connections()
key = f"{role}_url"
removed = saved.get(key, "")
if not removed:
raise HTTPException(404, f"No saved {role} Ollama endpoint")
saved[key] = ""
if role == "local":
saved["active_url"] = ""
_write_connections(saved)
if role == "local":
_apply_saved_connection()
return {"ok": True, "role": role, "removed_url": removed, "message": f"Removed saved {role} Ollama endpoint"}
@router.get("/status")
def status() -> dict[str, Any]:
tags = _local_tags()