fix: agent timeouts hid hf-pdocker-01; refresh links and groups
- agent: stats one_shot=True + 20 workers (.192 went 12s -> ~2s); /health ignores Dead containers - server: /containers fetch timeout 10s -> 30s (was silently returning [] for the busiest node) - links page: drop FRIDAY Voice, add Onyx, Guacamole, Synapse Admin, n8n, CouchDB; MCPO -> /docs, Trivy -> /healthz, Jellyseerr -> Seerr - config.json: drop dead gpu-workstation (192.168.86.27) - groups.json: rebuilt for current container names (14 groups) - includes earlier uncommitted work (models, styles, ports sorting) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQhgEUqcaV4WM2S4hfXgHD
This commit is contained in:
+95
-25
@@ -405,10 +405,18 @@ function renderServiceCard(s) {
|
||||
<span class="card-meta-label">Image</span>
|
||||
<span class="card-meta-value" title="${escHtml(s.image)}">${escHtml(imageDisplay)}</span>
|
||||
</div>
|
||||
${s.ports && s.ports.length ? `<div class="card-meta-row">
|
||||
<span class="card-meta-label">Ports</span>
|
||||
<span class="card-meta-value card-ports">${s.ports.map(p => `<span class="port-badge">${escHtml(p)}</span>`).join(" ")}</span>
|
||||
</div>` : ""}
|
||||
<div class="card-meta-row">
|
||||
<span class="card-meta-label">Up</span>
|
||||
<span class="card-meta-value">${escHtml(uptimeText)}</span>
|
||||
</div>
|
||||
${isRunning && s.memory_usage ? `<div class="card-meta-row">
|
||||
<span class="card-meta-label">Mem</span>
|
||||
<span class="card-meta-value">${formatMemory(s.memory_usage, s.memory_limit)}</span>
|
||||
</div>` : ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button class="card-action-btn action-start" title="Start"
|
||||
@@ -735,9 +743,13 @@ const serverLinks = [
|
||||
{ category: "Public Services", links: [
|
||||
{ name: "Home Assistant", url: "https://yoda.hobbs.farm", icon: "\uD83C\uDFE0" },
|
||||
{ name: "Matrix Chat", url: "https://mess.hobbs.farm", icon: "\uD83D\uDCAC" },
|
||||
{ name: "Matrix Auth (MAS)", url: "https://auth.hobbs.farm", icon: "\uD83D\uDD10" },
|
||||
{ name: "Guacamole", url: "https://solo.hobbs.farm/guacamole/", icon: "\uD83D\uDDA5\uFE0F" },
|
||||
{ name: "Vaultwarden", url: "https://vault.hobbs.farm", icon: "\uD83D\uDD12" },
|
||||
{ name: "Gitea", url: "https://git.hobbs.farm", icon: "\uD83D\uDCE6" },
|
||||
{ name: "Onyx", url: "https://onyx.hobbs.farm", icon: "\uD83D\uDD0D" },
|
||||
{ name: "LinkedStorm", url: "https://linkedstorm.hobbs.farm", icon: "\uD83D\uDD0A" },
|
||||
{ name: "Rongorongo", url: "https://rongorongo.hobbs.farm", icon: "\uD83D\uDDFF" },
|
||||
]},
|
||||
{ category: "hf-pdocker-01 (192.168.86.192)", links: [
|
||||
{ name: "Farm Manager", url: "http://192.168.86.192:8888", icon: "\uD83C\uDF3E" },
|
||||
@@ -747,20 +759,29 @@ const serverLinks = [
|
||||
{ name: "Grafana", url: "http://192.168.86.192:3001", icon: "\uD83D\uDCCA" },
|
||||
{ name: "Prometheus", url: "http://192.168.86.192:9095", icon: "\uD83D\uDD25" },
|
||||
{ name: "Alertmanager", url: "http://192.168.86.192:9093", icon: "\uD83D\uDEA8" },
|
||||
{ name: "AdGuard Home", url: "http://192.168.86.192:3000", icon: "\uD83D\uDEE1\uFE0F" },
|
||||
{ name: "Frigate Alerts", url: "http://192.168.86.192:5199", icon: "\uD83D\uDD14" },
|
||||
{ name: "Traefik Dashboard", url: "http://192.168.86.192:8090", icon: "\uD83D\uDEA6" },
|
||||
{ name: "Portainer", url: "http://192.168.86.192:9000", icon: "\uD83D\uDC33" },
|
||||
{ name: "LiteLLM", url: "http://192.168.86.192:4000/ui/login", icon: "\uD83E\uDD16" },
|
||||
{ name: "LinkedStorm", url: "http://192.168.86.192:8310", icon: "\uD83D\uDD0A" },
|
||||
{ name: "Rongorongo", url: "http://192.168.86.192:8181", icon: "\uD83D\uDDFF" },
|
||||
{ name: "Onyx", url: "http://192.168.86.192:3005", icon: "\uD83D\uDD0D" },
|
||||
{ name: "Guacamole", url: "http://192.168.86.192:8199/guacamole/", icon: "\uD83D\uDDA5\uFE0F" },
|
||||
{ name: "Synapse Admin", url: "http://192.168.86.192:8080", icon: "\uD83D\uDEE0\uFE0F" },
|
||||
]},
|
||||
{ category: "AI Agents (hf-pdocker-01)", links: [
|
||||
{ name: "Margaret", url: "http://192.168.86.192:18790", icon: "\uD83E\uDDD1\u200D\uD83D\uDCBB" },
|
||||
{ name: "Spirit", url: "http://192.168.86.192:18795", icon: "\uD83E\uDDD1\u200D\uD83D\uDCBB" },
|
||||
]},
|
||||
{ category: "hf-pdocker-02 (192.168.86.100)", links: [
|
||||
{ name: "Gitea", url: "http://192.168.86.100:3003", icon: "\uD83D\uDCE6" },
|
||||
{ name: "Wiki.js", url: "http://192.168.86.100:3002", icon: "\uD83D\uDCDA" },
|
||||
{ name: "Semaphore", url: "http://192.168.86.100:3004", icon: "\uD83C\uDFAF" },
|
||||
{ name: "Unifi Controller", url: "https://192.168.86.100:8443", icon: "\uD83D\uDCF6" },
|
||||
{ name: "Vaultwarden", url: "http://192.168.86.100:26226", icon: "\uD83D\uDD12" },
|
||||
{ name: "Actual Budget", url: "http://192.168.86.100:5006", icon: "\uD83D\uDCB0" },
|
||||
{ name: "MCPO", url: "http://192.168.86.100:8001", icon: "\uD83E\uDD16" },
|
||||
{ name: "Trivy", url: "http://192.168.86.100:4954", icon: "\uD83D\uDD0D" },
|
||||
{ name: "MCPO", url: "http://192.168.86.100:8001/docs", icon: "\uD83E\uDD16" },
|
||||
{ name: "Trivy server (API only)", url: "http://192.168.86.100:4954/healthz", icon: "\uD83D\uDD0D" },
|
||||
{ name: "SearXNG", url: "http://192.168.86.100:8888", icon: "\uD83D\uDD0E" },
|
||||
{ name: "n8n", url: "http://192.168.86.100:5678", icon: "\uD83D\uDD00" },
|
||||
{ name: "CouchDB (Fauxton)", url: "http://192.168.86.100:5984/_utils/", icon: "\uD83D\uDECB\uFE0F" },
|
||||
]},
|
||||
{ category: "bart (192.168.86.167)", links: [
|
||||
{ name: "Jellyfin", url: "http://192.168.86.167:8096", icon: "\uD83C\uDFAC" },
|
||||
@@ -768,8 +789,17 @@ const serverLinks = [
|
||||
{ name: "Radarr", url: "http://192.168.86.167:7878", icon: "\uD83C\uDFAC" },
|
||||
{ name: "SABnzbd", url: "http://192.168.86.167:8082", icon: "\u2B07\uFE0F" },
|
||||
{ name: "NZBHydra2", url: "http://192.168.86.167:5076", icon: "\uD83D\uDD0E" },
|
||||
{ name: "Jellyseerr", url: "http://192.168.86.167:5055", icon: "\u2B50" },
|
||||
{ name: "OpenVAS", url: "http://192.168.86.167:9392", icon: "\uD83D\uDEE1\uFE0F" },
|
||||
{ name: "Seerr", url: "http://192.168.86.167:5055", icon: "\u2B50" },
|
||||
]},
|
||||
// GPU Machine = the .150 Windows workstation running Docker Desktop.
|
||||
// hermes moved to hf-pdocker-01 (host network, no published port) and FRIDAY
|
||||
// Voice was decommissioned 2026-08-26, so neither has a link card.
|
||||
{ category: "GPU Machine (192.168.86.150)", links: [
|
||||
{ name: "UniFi OS Server (controller)", url: "https://192.168.86.150:11443", icon: "\uD83D\uDCF6" },
|
||||
{ name: "llama.cpp", url: "http://192.168.86.150:8081", icon: "\uD83E\uDDE0" },
|
||||
// speaches removed 2026-08-21 (VRAM freed for llama.cpp); re-add when rebuilt:
|
||||
// { name: "Speaches STT/TTS (GPU)", url: "http://192.168.86.150:8000", icon: "\uD83D\uDDE3\uFE0F" },
|
||||
{ name: "Ollama", url: "http://192.168.86.150:11434", icon: "\uD83E\uDD99" },
|
||||
]},
|
||||
{ category: "Infrastructure", links: [
|
||||
{ name: "Synology NAS", url: "https://192.168.86.30:5001", icon: "\uD83D\uDCBE" },
|
||||
@@ -778,24 +808,49 @@ const serverLinks = [
|
||||
|
||||
function renderLinksView() {
|
||||
const container = $("#links-view");
|
||||
container.innerHTML = `<h2 style="margin-bottom:20px;">Server Links</h2>` +
|
||||
serverLinks.map((cat) => `
|
||||
<div class="links-category">
|
||||
<div class="links-category-title">${escHtml(cat.category)}</div>
|
||||
<div class="links-grid">
|
||||
${cat.links.map((l) => `
|
||||
<a href="${escHtml(l.url)}" target="_blank" rel="noopener" class="link-card">
|
||||
<div class="link-icon">${l.icon}</div>
|
||||
<div class="link-info">
|
||||
<div class="link-name">${escHtml(l.name)}</div>
|
||||
<div class="link-url">${escHtml(l.url)}</div>
|
||||
</div>
|
||||
<span class="link-arrow">\u203A</span>
|
||||
</a>
|
||||
`).join("")}
|
||||
</div>
|
||||
const query = ($("#links-search") || {}).value || "";
|
||||
const q = query.toLowerCase();
|
||||
|
||||
const filtered = q
|
||||
? serverLinks.map((cat) => ({
|
||||
...cat,
|
||||
links: cat.links.filter((l) =>
|
||||
l.name.toLowerCase().includes(q) ||
|
||||
l.url.toLowerCase().includes(q) ||
|
||||
cat.category.toLowerCase().includes(q)
|
||||
),
|
||||
})).filter((cat) => cat.links.length > 0)
|
||||
: serverLinks;
|
||||
|
||||
const header = `<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;">
|
||||
<h2 style="margin:0;">Server Links</h2>
|
||||
<input type="text" id="links-search" class="search-input" placeholder="Search links..." value="${escHtml(query)}">
|
||||
</div>`;
|
||||
|
||||
const cats = filtered.map((cat) => `
|
||||
<div class="links-category">
|
||||
<div class="links-category-title">${escHtml(cat.category)}</div>
|
||||
<div class="links-grid">
|
||||
${cat.links.map((l) => `
|
||||
<a href="${escHtml(l.url)}" target="_blank" rel="noopener" class="link-card">
|
||||
<div class="link-icon">${l.icon}</div>
|
||||
<div class="link-info">
|
||||
<div class="link-name">${escHtml(l.name)}</div>
|
||||
<div class="link-url">${escHtml(l.url)}</div>
|
||||
</div>
|
||||
<span class="link-arrow">\u203A</span>
|
||||
</a>
|
||||
`).join("")}
|
||||
</div>
|
||||
`).join("");
|
||||
</div>
|
||||
`).join("");
|
||||
|
||||
container.innerHTML = header + (cats || `<div class="empty-state"><p>No links match your search.</p></div>`);
|
||||
|
||||
const searchInput = $("#links-search");
|
||||
searchInput.addEventListener("input", () => renderLinksView());
|
||||
searchInput.focus();
|
||||
searchInput.setSelectionRange(searchInput.value.length, searchInput.value.length);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -814,6 +869,21 @@ function capitalize(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return "0 B";
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + " KB";
|
||||
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(0) + " MB";
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + " GB";
|
||||
}
|
||||
|
||||
function formatMemory(usage, limit) {
|
||||
const usageStr = formatBytes(usage);
|
||||
if (limit && limit < 1e15) {
|
||||
return usageStr + " / " + formatBytes(limit);
|
||||
}
|
||||
return usageStr;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event bindings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user