diff --git a/server/static/app.js b/server/static/app.js index 8257ed8..2374b51 100644 --- a/server/static/app.js +++ b/server/static/app.js @@ -184,17 +184,34 @@ function renderGroupList() { } function updateSidebarActive() { - // All services link const allLink = $("#nav-all"); - if (allLink) { - allLink.classList.toggle("active", currentView === "all"); - } + const linksLink = $("#nav-links"); + if (allLink) allLink.classList.toggle("active", currentView === "all"); + if (linksLink) linksLink.classList.toggle("active", currentView === "links"); } // --------------------------------------------------------------------------- // Main content rendering // --------------------------------------------------------------------------- function renderMain() { + const linksView = $("#links-view"); + const toolbar = $("#toolbar"); + const grid = $("#service-grid"); + const emptyState = $("#empty-state"); + + if (currentView === "links") { + toolbar.style.display = "none"; + grid.style.display = "none"; + emptyState.style.display = "none"; + linksView.style.display = "block"; + renderLinksView(); + return; + } + + toolbar.style.display = "flex"; + grid.style.display = "grid"; + linksView.style.display = "none"; + const filtered = getFilteredServices(); renderToolbar(filtered); renderServices(filtered); @@ -711,6 +728,75 @@ function updateRefreshIndicator() { indicator.classList.toggle("paused", logPanelOpen || modalOpen); } +// --------------------------------------------------------------------------- +// Links page +// --------------------------------------------------------------------------- +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: "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" }, + ]}, + { category: "hf-pdocker-01 (192.168.86.192)", links: [ + { name: "Farm Manager", url: "http://192.168.86.192:8888", icon: "\uD83C\uDF3E" }, + { name: "Frigate NVR", url: "http://192.168.86.192:5000", icon: "\uD83D\uDCF7" }, + { name: "Home Assistant", url: "http://192.168.86.192:8123", icon: "\uD83C\uDFE0" }, + { name: "ESPHome", url: "http://192.168.86.192:6052", icon: "\uD83D\uDD0C" }, + { 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: "Traefik Dashboard", url: "http://192.168.86.192:8090", icon: "\uD83D\uDEA6" }, + { name: "Portainer", url: "http://192.168.86.192:9000", icon: "\uD83D\uDC33" }, + ]}, + { 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" }, + ]}, + { category: "bart (192.168.86.167)", links: [ + { name: "Jellyfin", url: "http://192.168.86.167:8096", icon: "\uD83C\uDFAC" }, + { name: "Sonarr", url: "http://192.168.86.167:8989", icon: "\uD83D\uDCFA" }, + { 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" }, + ]}, + { category: "Infrastructure", links: [ + { name: "Synology NAS", url: "https://192.168.86.30:5001", icon: "\uD83D\uDCBE" }, + ]}, +]; + +function renderLinksView() { + const container = $("#links-view"); + container.innerHTML = `

Server Links

` + + serverLinks.map((cat) => ` + + `).join(""); +} + // --------------------------------------------------------------------------- // Utilities // --------------------------------------------------------------------------- @@ -742,6 +828,17 @@ function bindEvents() { }); } + // Links nav + const linksNav = $("#nav-links"); + if (linksNav) { + linksNav.addEventListener("click", (e) => { + e.preventDefault(); + currentView = "links"; + renderSidebar(); + renderMain(); + }); + } + // Status filter pills $$(".filter-pill").forEach((pill) => { pill.addEventListener("click", () => { diff --git a/server/static/index.html b/server/static/index.html index 614a3e7..50758d4 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -25,6 +25,9 @@ All Services + + 🔗 Server Links + + + + diff --git a/server/static/style.css b/server/static/style.css index b83f48c..a2a8927 100644 --- a/server/static/style.css +++ b/server/static/style.css @@ -1078,6 +1078,90 @@ body { font-size: 15px; } +/* ===== Links Page ===== */ +.links-category { + margin-bottom: 28px; +} + +.links-category-title { + font-size: 13px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-secondary); + margin-bottom: 10px; + padding-bottom: 6px; + border-bottom: 1px solid var(--border); +} + +.links-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 10px; +} + +.link-card { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--card-radius); + text-decoration: none; + color: var(--text-primary); + transition: border-color var(--transition), box-shadow var(--transition), background var(--transition); +} + +.link-card:hover { + border-color: var(--accent); + box-shadow: 0 0 0 1px rgba(88, 166, 255, 0.15); + background: var(--bg-tertiary); +} + +.link-icon { + width: 36px; + height: 36px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + flex-shrink: 0; + background: var(--bg-tertiary); +} + +.link-info { + flex: 1; + min-width: 0; +} + +.link-name { + font-size: 14px; + font-weight: 600; + line-height: 1.3; +} + +.link-url { + font-size: 11px; + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.link-arrow { + color: var(--text-secondary); + font-size: 16px; + flex-shrink: 0; + transition: color var(--transition), transform var(--transition); +} + +.link-card:hover .link-arrow { + color: var(--accent); + transform: translateX(2px); +} + /* ===== Responsive ===== */ @media (max-width: 1200px) { .service-grid {