From 4bb9d9b123020fa9eb1ad33744c98095577e3459 Mon Sep 17 00:00:00 2001 From: Mirza Hasanbasic Date: Mon, 23 Mar 2026 20:32:51 +0100 Subject: [PATCH] UPDATE --- assets/css/style.css | 130 ++++++++++++++++++++++++++++++++++++----- assets/js/main.js | 136 ++++++++++++++++++++++++++++++++++++++----- services.json | 102 +++++++++++++++++++------------- 3 files changed, 295 insertions(+), 73 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index ef0a382..7de6537 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -235,14 +235,15 @@ a { position: relative; display: flex; flex-direction: column; - align-items: center; - text-align: center; - padding: 28px 16px 24px; + align-items: stretch; + text-align: left; + gap: 12px; + min-height: 248px; + padding: 22px 18px 18px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease; - cursor: pointer; animation: fadeInUp 0.5s ease both; } @@ -253,32 +254,104 @@ a { background: var(--bg-card-hover); } -.service-card i { - font-size: 32px; +.service-card-link { + display: flex; + align-items: flex-start; + gap: 14px; +} + +.service-card-link i { + font-size: 28px; color: var(--accent); - margin-bottom: 12px; + flex-shrink: 0; transition: transform 0.25s ease; } -.service-card:hover i { - transform: scale(1.1); +.service-card:hover .service-card-link i { + transform: scale(1.08); +} + +.card-heading { + display: flex; + flex-direction: column; + min-width: 0; } .card-name { - font-size: 14px; + font-size: 15px; font-weight: 600; - color: var(--text); - line-height: 1.3; + color: var(--text-heading); + line-height: 1.35; } .card-sub { font-size: 11px; color: var(--text-muted); - margin-top: 6px; + margin-top: 4px; font-family: 'Inter', monospace; opacity: 0.7; } +.card-description { + font-size: 13px; + color: var(--text-muted); + line-height: 1.5; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.card-tags { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.card-tag { + display: inline-flex; + align-items: center; + padding: 4px 8px; + border: 1px solid rgba(255, 196, 81, 0.16); + border-radius: 999px; + background: rgba(255, 196, 81, 0.08); + font-size: 10px; + font-weight: 600; + line-height: 1; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--accent); +} + +.card-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: auto; + padding-top: 8px; +} + +.service-action { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 30px; + padding: 0 10px; + border: 1px solid var(--border); + border-radius: 999px; + background: rgba(255, 255, 255, 0.03); + color: var(--text); + font-size: 12px; + font-weight: 600; + transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease; +} + +.service-action:hover { + border-color: var(--border-hover); + background: rgba(255, 196, 81, 0.1); + color: var(--text-heading); +} + /* Staggered animation */ .service-card:nth-child(1) { animation-delay: 0.02s; } .service-card:nth-child(2) { animation-delay: 0.04s; } @@ -522,7 +595,7 @@ a { .search-result-item { display: flex; - align-items: center; + align-items: flex-start; gap: 12px; padding: 12px 20px; cursor: pointer; @@ -538,14 +611,31 @@ a { font-size: 18px; color: var(--accent); flex-shrink: 0; + margin-top: 2px; } -.search-result-item span { +.search-result-body { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; +} + +.search-result-title { font-size: 14px; - font-weight: 500; + font-weight: 600; color: var(--text); } +.search-result-desc, +.search-result-meta { + font-size: 12px; + color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .search-result-empty { padding: 24px 20px; text-align: center; @@ -634,6 +724,14 @@ a { /* ── Mobile Adjustments ── */ @media (max-width: 768px) { + .card-grid { + grid-template-columns: 1fr; + } + + .service-card { + min-height: 0; + } + .search-overlay { padding: 8vh 16px 0; } diff --git a/assets/js/main.js b/assets/js/main.js index c0feff1..eb7da91 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -8,6 +8,30 @@ return res.json(); } + function buildSearchText(svc) { + const linkParts = (svc.links || []).flatMap(link => [link.label, link.url]); + return [ + svc.name, + svc.description, + svc.sub, + ...(svc.tags || []), + ...linkParts + ].filter(Boolean).join(' '); + } + + function getServiceActions(svc) { + const actions = [{ label: 'Open', url: svc.url }, ...(svc.links || [])]; + const seen = new Set(); + + return actions.filter(action => { + if (!action?.label || !action?.url) return false; + const key = `${action.label}::${action.url}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + } + // ── Nav Renderer ── function renderNav(config) { const nav = document.getElementById('nav'); @@ -55,11 +79,11 @@ grid.className = 'card-grid'; services.forEach((svc, i) => { - const card = document.createElement('a'); - card.href = svc.url; + const card = document.createElement('article'); card.className = 'service-card'; card.dataset.name = svc.name.toLowerCase(); card.dataset.category = cat.id; + card.dataset.searchText = buildSearchText(svc).toLowerCase(); card.style.animationDelay = `${(i + 1) * 0.02}s`; // Status dot @@ -67,20 +91,67 @@ dot.className = 'card-status-dot unknown'; card.appendChild(dot); + const primaryLink = document.createElement('a'); + primaryLink.href = svc.url; + primaryLink.className = 'service-card-link'; + const icon = document.createElement('i'); icon.className = svc.icon; - card.appendChild(icon); + primaryLink.appendChild(icon); + + const heading = document.createElement('div'); + heading.className = 'card-heading'; const name = document.createElement('span'); name.className = 'card-name'; name.textContent = svc.name; - card.appendChild(name); + heading.appendChild(name); if (svc.sub) { const sub = document.createElement('span'); sub.className = 'card-sub'; sub.textContent = svc.sub; - card.appendChild(sub); + heading.appendChild(sub); + } + + primaryLink.appendChild(heading); + card.appendChild(primaryLink); + + if (svc.description) { + const description = document.createElement('p'); + description.className = 'card-description'; + description.textContent = svc.description; + card.appendChild(description); + } + + if (Array.isArray(svc.tags) && svc.tags.length > 0) { + const tags = document.createElement('div'); + tags.className = 'card-tags'; + + svc.tags.forEach(tagText => { + const tag = document.createElement('span'); + tag.className = 'card-tag'; + tag.textContent = tagText; + tags.appendChild(tag); + }); + + card.appendChild(tags); + } + + const actions = getServiceActions(svc); + if (actions.length > 0) { + const actionsWrap = document.createElement('div'); + actionsWrap.className = 'card-actions'; + + actions.forEach(action => { + const link = document.createElement('a'); + link.href = action.url; + link.className = 'service-action'; + link.textContent = action.label; + actionsWrap.appendChild(link); + }); + + card.appendChild(actionsWrap); } grid.appendChild(card); @@ -169,7 +240,6 @@ if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); - console.log(`[StatusMonitor] ${mon.label} raw:`, JSON.stringify(data).slice(0, 500)); const result = this.parsers[mon.parseType](data); dot.className = 'status-dot ok'; value.textContent = result; @@ -201,19 +271,23 @@ const dot = card.querySelector('.card-status-dot'); if (!dot) return; + const url = svc.healthCheckUrl; + const isRelative = url.startsWith('/'); + const isHttps = url.startsWith('https://'); + + // HTTPS cross-origin can't be reliably checked from the browser + // (redirects, CORS headers, etc.) — leave as unknown + if (isHttps) return; + dot.className = 'card-status-dot loading'; try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000); - const url = svc.healthCheckUrl; - const isRelative = url.startsWith('/'); - const isHttps = url.startsWith('https://'); - - // Relative URLs and HTTPS: normal fetch. HTTP cross-origin: no-cors. + // Relative URLs (/proxy/...): normal fetch. HTTP same-network: no-cors const fetchOpts = { signal: controller.signal }; - if (!isRelative && !isHttps) { + if (!isRelative) { fetchOpts.mode = 'no-cors'; } @@ -320,14 +394,44 @@ this.results.innerHTML = ''; if (!query) return; - const matches = this.services.filter(svc => this.fuzzyMatch(query, svc.name)); + const matches = this.services.filter(svc => this.fuzzyMatch(query, buildSearchText(svc))).slice(0, 10); matches.forEach((svc, i) => { const item = document.createElement('div'); item.className = 'search-result-item' + (i === this.selectedIndex ? ' active' : ''); item.dataset.url = svc.url; - item.innerHTML = `${svc.name}`; + const icon = document.createElement('i'); + icon.className = svc.icon; + + const body = document.createElement('div'); + body.className = 'search-result-body'; + + const title = document.createElement('span'); + title.className = 'search-result-title'; + title.textContent = svc.name; + body.appendChild(title); + + if (svc.description) { + const desc = document.createElement('span'); + desc.className = 'search-result-desc'; + desc.textContent = svc.description; + body.appendChild(desc); + } + + const metaParts = []; + if (svc.sub) metaParts.push(svc.sub); + if (Array.isArray(svc.tags) && svc.tags.length > 0) metaParts.push(svc.tags.join(' · ')); + + if (metaParts.length > 0) { + const meta = document.createElement('span'); + meta.className = 'search-result-meta'; + meta.textContent = metaParts.join(' | '); + body.appendChild(meta); + } + + item.appendChild(icon); + item.appendChild(body); item.addEventListener('click', () => { window.location.href = svc.url; @@ -368,8 +472,8 @@ } allCards.forEach(card => { - const name = card.dataset.name || ''; - const match = this.fuzzyMatch(query, name); + const searchText = card.dataset.searchText || card.dataset.name || ''; + const match = this.fuzzyMatch(query, searchText); card.classList.toggle('search-hidden', !match); card.classList.toggle('search-highlight', match); }); diff --git a/services.json b/services.json index 8e0f9a8..3927896 100644 --- a/services.json +++ b/services.json @@ -1,54 +1,74 @@ { "categories": [ - { "id": "infrastructure", "title": "Infrastructure & Security", "icon": "ri-shield-check-line", "navLabel": "Infra", "showInNav": true }, - { "id": "monitoring", "title": "Monitoring & Analytics", "icon": "ri-pulse-line", "navLabel": "Monitoring", "showInNav": true }, - { "id": "development", "title": "Development & Tools", "icon": "ri-code-s-slash-line", "navLabel": "Dev", "showInNav": true }, - { "id": "media-streaming", "title": "Media Streaming", "icon": "ri-film-line", "navLabel": "Media", "showInNav": true }, - { "id": "media-automation", "title": "Media Automation", "icon": "ri-robot-line", "navLabel": "Automation", "showInNav": false }, - { "id": "tesla", "title": "Tesla", "icon": "ri-car-line", "navLabel": "Tesla", "showInNav": true }, - { "id": "productivity", "title": "Productivity & Utilities", "icon": "ri-tools-line", "navLabel": "Tools", "showInNav": false } + { "id": "smart-home", "title": "Smart Home", "icon": "ri-home-5-line", "navLabel": "Smart", "showInNav": true }, + { "id": "media", "title": "Media (Seedbox)", "icon": "ri-film-line", "navLabel": "Media", "showInNav": true }, + { "id": "productivity", "title": "Productivity", "icon": "ri-tools-line", "navLabel": "Productivity", "showInNav": true }, + { "id": "ai-chat", "title": "AI / Chat", "icon": "ri-chat-smile-ai-line", "navLabel": "AI", "showInNav": true }, + { "id": "devops", "title": "DevOps / Code", "icon": "ri-code-s-slash-line", "navLabel": "DevOps", "showInNav": true }, + { "id": "monitoring", "title": "Monitoring", "icon": "ri-pulse-line", "navLabel": "Monitoring", "showInNav": true }, + { "id": "vehicle", "title": "Vehicle", "icon": "ri-car-line", "navLabel": "Vehicle", "showInNav": true }, + { "id": "infrastructure", "title": "Infrastructure", "icon": "ri-shield-check-line", "navLabel": "Infra", "showInNav": true }, + { "id": "nas", "title": "NAS (Synology - 192.168.1.22)", "icon": "ri-hard-drive-3-line", "navLabel": "NAS", "showInNav": true }, + { "id": "k3s-services", "title": "K3S Services", "icon": "ri-cloud-line", "navLabel": "K3S", "showInNav": true }, + { "id": "k3s-monitoring", "title": "K3S Monitoring", "icon": "ri-bar-chart-box-line", "navLabel": "K3S Mon", "showInNav": true } ], "services": [ - { "name": "AdGuard Home", "url": "http://adguard.local", "icon": "ri-shield-check-line", "category": "infrastructure", "healthCheckUrl": "http://adguard.local" }, - { "name": "Omada Controller","url": "http://omada.local", "icon": "ri-wifi-line", "category": "infrastructure", "healthCheckUrl": "http://omada.local" }, - { "name": "Authentik", "url": "https://auth.hasanbasic.dk", "icon": "ri-login-box-fill", "category": "infrastructure", "healthCheckUrl": "https://auth.hasanbasic.dk" }, - { "name": "Longhorn", "url": "http://longhorn.local", "icon": "ri-storage-line", "category": "infrastructure", "healthCheckUrl": "http://longhorn.local" }, - { "name": "Rancher", "url": "http://rancher.local", "icon": "ri-dashboard-line", "category": "infrastructure", "healthCheckUrl": "http://rancher.local" }, - { "name": "Synology NAS", "url": "http://nas.local", "icon": "ri-server-line", "category": "infrastructure", "healthCheckUrl": "http://nas.local" }, + { "name": "Home Assistant", "url": "http://192.168.1.13:8123", "icon": "ri-home-5-line", "category": "smart-home", "sub": "homeassistant.local", "description": "Primary smart-home control plane for automations, dashboards, and device state.", "tags": ["automation", "iot", "local"], "links": [{ "label": "Local", "url": "http://homeassistant.local" }], "healthCheckUrl": "http://192.168.1.13:8123" }, + { "name": "Zigbee2MQTT", "url": "http://192.168.1.13:8090", "icon": "ri-router-line", "category": "smart-home", "sub": "zigbee.local", "description": "Zigbee bridge and device management UI for the local home network.", "tags": ["zigbee", "iot", "bridge"], "links": [{ "label": "Local", "url": "http://zigbee.local" }], "healthCheckUrl": "http://192.168.1.13:8090" }, + { "name": "Mosquitto MQTT", "url": "http://192.168.1.13:9003", "icon": "ri-broadcast-line", "category": "smart-home", "sub": "mqtt:1885 | ws:9003", "description": "MQTT broker endpoint for device messaging with WebSocket access on the side.", "tags": ["mqtt", "broker", "transport"] }, - { "name": "Uptime Kuma", "url": "http://192.168.1.13:3010", "icon": "ri-heart-pulse-line", "category": "monitoring", "sub": "192.168.1.13:3010", "healthCheckUrl": "/proxy/uptime/" }, - { "name": "Glances", "url": "http://192.168.1.13:61208", "icon": "ri-eye-line", "category": "monitoring", "sub": "192.168.1.13:61208", "healthCheckUrl": "/proxy/glances/" }, - { "name": "cAdvisor", "url": "http://192.168.1.13:8084", "icon": "ri-container-line", "category": "monitoring", "sub": "192.168.1.13:8084", "healthCheckUrl": "/proxy/cadvisor/" }, - { "name": "Umami", "url": "https://umami.hasanbasic.dk", "icon": "ri-bar-chart-box-line", "category": "monitoring", "sub": "umami.hasanbasic.dk", "healthCheckUrl": "https://umami.hasanbasic.dk" }, + { "name": "Plex", "url": "http://192.168.1.13:32400/web", "icon": "ri-film-line", "category": "media", "sub": "plex.local", "description": "Main media server for browsing and streaming the local library.", "tags": ["media", "streaming", "library"], "links": [{ "label": "Local", "url": "http://plex.local" }], "healthCheckUrl": "http://192.168.1.13:32400/web" }, + { "name": "Overseerr", "url": "http://192.168.1.13:5055", "icon": "ri-movie-2-line", "category": "media", "sub": "overseerr.local | overseerr.hasanbasic.dk", "description": "Request portal for adding movies and shows into the media automation stack.", "tags": ["requests", "media", "public"], "links": [{ "label": "Local", "url": "http://overseerr.local" }, { "label": "Public", "url": "https://overseerr.hasanbasic.dk" }], "healthCheckUrl": "http://192.168.1.13:5055" }, + { "name": "Ombi", "url": "http://192.168.1.13:3579", "icon": "ri-hand-heart-fill", "category": "media", "sub": "ombi.local", "description": "Legacy request interface for media discovery and user submissions.", "tags": ["requests", "media", "legacy"], "links": [{ "label": "Local", "url": "http://ombi.local" }], "healthCheckUrl": "http://192.168.1.13:3579" }, + { "name": "Sonarr", "url": "http://192.168.1.13:8989", "icon": "ri-tv-line", "category": "media", "sub": "sonarr.local", "description": "TV series automation for indexers, downloads, and library organization.", "tags": ["tv", "automation", "arr"], "links": [{ "label": "Local", "url": "http://sonarr.local" }], "healthCheckUrl": "http://192.168.1.13:8989" }, + { "name": "Radarr", "url": "http://192.168.1.13:7878", "icon": "ri-movie-line", "category": "media", "sub": "radarr.local", "description": "Movie automation service for acquisition, upgrades, and import handling.", "tags": ["movies", "automation", "arr"], "links": [{ "label": "Local", "url": "http://radarr.local" }], "healthCheckUrl": "http://192.168.1.13:7878" }, + { "name": "Lidarr", "url": "http://192.168.1.13:8686", "icon": "ri-music-line", "category": "media", "sub": "lidarr.local", "description": "Music library automation for artist monitoring and release acquisition.", "tags": ["music", "automation", "arr"], "links": [{ "label": "Local", "url": "http://lidarr.local" }], "healthCheckUrl": "http://192.168.1.13:8686" }, + { "name": "Readarr", "url": "http://192.168.1.13:8787", "icon": "ri-book-read-line", "category": "media", "sub": "readarr.local", "description": "Book and ebook automation service for tracking authors and releases.", "tags": ["books", "automation", "arr"], "links": [{ "label": "Local", "url": "http://readarr.local" }], "healthCheckUrl": "http://192.168.1.13:8787" }, + { "name": "Bazarr", "url": "http://192.168.1.13:6767", "icon": "ri-translate", "category": "media", "sub": "bazarr.local", "description": "Subtitle management for movies and TV with automatic sync workflows.", "tags": ["subtitles", "automation", "media"], "links": [{ "label": "Local", "url": "http://bazarr.local" }], "healthCheckUrl": "http://192.168.1.13:6767" }, + { "name": "Prowlarr", "url": "http://192.168.1.13:9696", "icon": "ri-search-line", "category": "media", "sub": "prowlarr.local", "description": "Central indexer manager shared across the media automation services.", "tags": ["indexers", "automation", "arr"], "links": [{ "label": "Local", "url": "http://prowlarr.local" }], "healthCheckUrl": "http://192.168.1.13:9696" }, + { "name": "qBittorrent", "url": "http://192.168.1.13:8080", "icon": "ri-download-cloud-2-line", "category": "media", "sub": "192.168.1.13:8080", "description": "Torrent download client used by the media automation stack.", "tags": ["downloads", "torrent", "media"], "healthCheckUrl": "http://192.168.1.13:8080" }, + { "name": "FlareSolverr", "url": "http://192.168.1.13:8191", "icon": "ri-shield-flash-line", "category": "media", "sub": "192.168.1.13:8191", "description": "Anti-bot challenge helper used when indexers sit behind browser checks.", "tags": ["automation", "anti-bot", "support"], "healthCheckUrl": "http://192.168.1.13:8191" }, + { "name": "Dispatcharr", "url": "http://192.168.1.13:9191", "icon": "ri-send-plane-line", "category": "media", "sub": "192.168.1.13:9191", "description": "Workflow orchestration and queue handling for the ARR ecosystem.", "tags": ["automation", "queue", "support"], "healthCheckUrl": "http://192.168.1.13:9191" }, + { "name": "Tracearr", "url": "http://192.168.1.13:3333", "icon": "ri-radar-line", "category": "media", "sub": "192.168.1.13:3333", "description": "Tracing and diagnostics UI for troubleshooting media pipeline behavior.", "tags": ["diagnostics", "observability", "media"], "healthCheckUrl": "http://192.168.1.13:3333" }, - { "name": "Code Server", "url": "https://code.mirzahasanbasic.dk", "icon": "ri-code-s-slash-line", "category": "development", "healthCheckUrl": "https://code.mirzahasanbasic.dk" }, - { "name": "Gitea", "url": "https://git.mirzahasanbasic.dk", "icon": "ri-git-repository-line", "category": "development", "healthCheckUrl": "https://git.mirzahasanbasic.dk" }, - { "name": "pgAdmin", "url": "http://pgadmin.local", "icon": "ri-database-2-line", "category": "development", "healthCheckUrl": "http://pgadmin.local" }, - { "name": "Portainer", "url": "http://portainer.local", "icon": "ri-ship-line", "category": "development", "healthCheckUrl": "http://portainer.local" }, - { "name": "MinIO", "url": "http://minio.local", "icon": "ri-database-line", "category": "development", "healthCheckUrl": "http://minio.local" }, - { "name": "Histogram", "url": "https://histogram.hasanbasic.dk", "icon": "ri-database-2-line", "category": "development", "healthCheckUrl": "https://histogram.hasanbasic.dk" }, - { "name": "LibreChat", "url": "http://192.168.1.13:3080", "icon": "ri-chat-smile-ai-line", "category": "development", "sub": "192.168.1.13:3080", "healthCheckUrl": "http://192.168.1.13:3080" }, + { "name": "Paperless", "url": "http://192.168.1.13:8000", "icon": "ri-file-paper-2-line", "category": "productivity", "sub": "paperless.local", "description": "Document archive for scanning, tagging, and retrieving personal paperwork.", "tags": ["documents", "archive", "local"], "links": [{ "label": "Local", "url": "http://paperless.local" }], "healthCheckUrl": "http://192.168.1.13:8000" }, + { "name": "Tandoor Recipes", "url": "http://192.168.1.13:8089", "icon": "ri-restaurant-line", "category": "productivity", "sub": "tandoor.local", "description": "Recipe planning and kitchen reference system for meal organization.", "tags": ["recipes", "planning", "home"], "links": [{ "label": "Local", "url": "http://tandoor.local" }], "healthCheckUrl": "http://192.168.1.13:8089" }, + { "name": "Stirling PDF", "url": "http://192.168.1.13:8079", "icon": "ri-file-pdf-2-line", "category": "productivity", "sub": "stirling-pdf.local", "description": "Browser-based PDF toolkit for merging, splitting, and document cleanup.", "tags": ["pdf", "tools", "documents"], "links": [{ "label": "Local", "url": "http://stirling-pdf.local" }], "healthCheckUrl": "http://192.168.1.13:8079" }, + { "name": "Firefly III", "url": "http://192.168.1.13:901", "icon": "ri-money-dollar-circle-line", "category": "productivity", "sub": "192.168.1.13:901", "description": "Personal finance manager for budgets, accounts, and transaction tracking.", "tags": ["finance", "budget", "self-hosted"], "healthCheckUrl": "http://192.168.1.13:901" }, + { "name": "Firefly Importer", "url": "http://192.168.1.13:902", "icon": "ri-exchange-dollar-line", "category": "productivity", "sub": "192.168.1.13:902", "description": "Import pipeline for feeding statements and bank exports into Firefly III.", "tags": ["finance", "import", "support"], "healthCheckUrl": "http://192.168.1.13:902" }, - { "name": "Plex", "url": "https://plex.mirzahasanbasic.dk", "icon": "ri-film-line", "category": "media-streaming", "healthCheckUrl": "https://plex.mirzahasanbasic.dk" }, - { "name": "Overseerr", "url": "https://overseer.hasanbasic.dk", "icon": "ri-movie-2-line", "category": "media-streaming", "healthCheckUrl": "https://overseer.hasanbasic.dk" }, - { "name": "Immich", "url": "https://immich.hasanbasic.dk", "icon": "ri-image-line", "category": "media-streaming", "healthCheckUrl": "https://immich.hasanbasic.dk" }, - { "name": "Ombi", "url": "http://ombi.local", "icon": "ri-hand-heart-fill", "category": "media-streaming", "healthCheckUrl": "http://ombi.local" }, + { "name": "LibreChat", "url": "http://192.168.1.13:3080", "icon": "ri-chat-smile-ai-line", "category": "ai-chat", "sub": "192.168.1.13:3080", "description": "Self-hosted chat interface for LLM access, prompts, and conversation workflows.", "tags": ["ai", "chat", "llm"], "healthCheckUrl": "http://192.168.1.13:3080" }, - { "name": "Sonarr", "url": "http://sonarr.local", "icon": "ri-tv-line", "category": "media-automation", "healthCheckUrl": "http://sonarr.local" }, - { "name": "Radarr", "url": "http://radarr.local", "icon": "ri-movie-line", "category": "media-automation", "healthCheckUrl": "http://radarr.local" }, - { "name": "Prowlarr", "url": "http://prowlarr.local", "icon": "ri-search-line", "category": "media-automation", "healthCheckUrl": "http://prowlarr.local" }, - { "name": "Lidarr", "url": "http://lidarr.local", "icon": "ri-music-line", "category": "media-automation", "healthCheckUrl": "http://lidarr.local" }, - { "name": "Readarr", "url": "http://readarr.local", "icon": "ri-book-read-line", "category": "media-automation", "healthCheckUrl": "http://readarr.local" }, - { "name": "Bazarr", "url": "http://bazarr.local", "icon": "ri-translate", "category": "media-automation", "healthCheckUrl": "http://bazarr.local" }, + { "name": "Gitea", "url": "http://192.168.1.13:3000", "icon": "ri-git-repository-line", "category": "devops", "sub": "git.local", "description": "Git forge for source control, pull requests, and lightweight project hosting.", "tags": ["git", "code", "forge"], "links": [{ "label": "Local", "url": "http://git.local" }], "healthCheckUrl": "http://192.168.1.13:3000" }, + { "name": "Grafana (HA)", "url": "http://192.168.1.13:3004", "icon": "ri-line-chart-line", "category": "devops", "sub": "grafana-ha.local", "description": "Grafana instance focused on Home Assistant dashboards and automation metrics.", "tags": ["grafana", "dashboards", "home-assistant"], "links": [{ "label": "Local", "url": "http://grafana-ha.local" }], "healthCheckUrl": "http://192.168.1.13:3004" }, + { "name": "Histogram", "url": "http://192.168.1.13:8002", "icon": "ri-database-2-line", "category": "devops", "sub": "histogram.local", "description": "Database and analytics workspace for inspecting structured application data.", "tags": ["analytics", "database", "dev"], "links": [{ "label": "Local", "url": "http://histogram.local" }], "healthCheckUrl": "http://192.168.1.13:8002" }, - { "name": "TeslaMate", "url": "http://teslamate.local", "icon": "ri-car-line", "category": "tesla", "healthCheckUrl": "http://teslamate.local" }, - { "name": "Tesla Charts", "url": "http://tesla-grafana.local", "icon": "ri-line-chart-line", "category": "tesla", "healthCheckUrl": "http://tesla-grafana.local" }, - { "name": "Tesla DB", "url": "http://tesla-pgadmin.local", "icon": "ri-database-line", "category": "tesla", "healthCheckUrl": "http://tesla-pgadmin.local" }, + { "name": "Uptime Kuma", "url": "http://192.168.1.13:3010", "icon": "ri-heart-pulse-line", "category": "monitoring", "sub": "uptime.local", "description": "Availability monitor and alert overview for local services and endpoints.", "tags": ["uptime", "alerts", "monitoring"], "links": [{ "label": "Local", "url": "http://uptime.local" }], "healthCheckUrl": "/proxy/uptime/" }, + { "name": "Glances", "url": "http://192.168.1.13:61208", "icon": "ri-eye-line", "category": "monitoring", "sub": "192.168.1.13:61208", "description": "Live system metrics for CPU, memory, load, and host resource visibility.", "tags": ["metrics", "host", "monitoring"], "healthCheckUrl": "/proxy/glances/" }, + { "name": "cAdvisor", "url": "http://192.168.1.13:8084", "icon": "ri-container-line", "category": "monitoring", "sub": "192.168.1.13:8084", "description": "Container runtime insights for resource usage and host-level container stats.", "tags": ["containers", "metrics", "monitoring"], "healthCheckUrl": "/proxy/cadvisor/" }, + { "name": "Umami", "url": "http://192.168.1.13:3018", "icon": "ri-bar-chart-box-line", "category": "monitoring", "sub": "umami.local | umami.hasanbasic.dk", "description": "Web analytics dashboard for tracking visits, events, and site usage trends.", "tags": ["analytics", "web", "public"], "links": [{ "label": "Local", "url": "http://umami.local" }, { "label": "Public", "url": "https://umami.hasanbasic.dk" }], "healthCheckUrl": "http://192.168.1.13:3018" }, - { "name": "PDF Tools", "url": "http://stirling-pdf.local", "icon": "ri-file-pdf-2-line", "category": "productivity", "healthCheckUrl": "http://stirling-pdf.local" }, - { "name": "BookStack", "url": "https://bookstack.hasanbasic.dk", "icon": "ri-book-line", "category": "productivity", "healthCheckUrl": "https://bookstack.hasanbasic.dk" }, - { "name": "Tandoor", "url": "http://192.168.1.13:8089", "icon": "ri-restaurant-line", "category": "productivity", "sub": "192.168.1.13:8089", "healthCheckUrl": "http://192.168.1.13:8089" }, - { "name": "Paperless", "url": "http://192.168.1.13:8000", "icon": "ri-file-paper-2-line", "category": "productivity", "sub": "192.168.1.13:8000", "healthCheckUrl": "http://192.168.1.13:8000" } + { "name": "TeslaMate", "url": "http://192.168.1.13:4001", "icon": "ri-car-line", "category": "vehicle", "sub": "teslamate.local", "description": "Vehicle telemetry capture and trip history dashboard for Tesla data.", "tags": ["tesla", "telemetry", "vehicle"], "links": [{ "label": "Local", "url": "http://teslamate.local" }], "healthCheckUrl": "http://192.168.1.13:4001" }, + { "name": "TeslaMate Grafana", "url": "http://192.168.1.13:3001", "icon": "ri-line-chart-line", "category": "vehicle", "sub": "tesla-grafana.local", "description": "Grafana dashboards for charge history, routes, and TeslaMate-derived metrics.", "tags": ["tesla", "grafana", "dashboards"], "links": [{ "label": "Local", "url": "http://tesla-grafana.local" }], "healthCheckUrl": "http://192.168.1.13:3001" }, + { "name": "TeslaMate PgAdmin", "url": "http://192.168.1.13:3003", "icon": "ri-database-line", "category": "vehicle", "sub": "tesla-pgadmin.local", "description": "Database administration UI for TeslaMate data inspection and maintenance.", "tags": ["tesla", "database", "admin"], "links": [{ "label": "Local", "url": "http://tesla-pgadmin.local" }], "healthCheckUrl": "http://192.168.1.13:3003" }, + + { "name": "Mailcow", "url": "https://192.168.1.13:8443", "icon": "ri-mail-line", "category": "infrastructure", "sub": "mail.local | mail.hasanbasic.dk", "description": "Mail platform for domain inboxes, administration, and mail flow management.", "tags": ["mail", "infrastructure", "public"], "links": [{ "label": "Local", "url": "http://mail.local" }, { "label": "Public", "url": "https://mail.hasanbasic.dk" }], "healthCheckUrl": "https://192.168.1.13:8443" }, + { "name": "RabbitMQ", "url": "http://192.168.1.13:15672", "icon": "ri-node-tree", "category": "infrastructure", "sub": "192.168.1.13:15672", "description": "Message broker management console for queue, exchange, and consumer operations.", "tags": ["messaging", "broker", "infra"], "healthCheckUrl": "http://192.168.1.13:15672" }, + { "name": "InfluxDB", "url": "http://192.168.1.13:8087", "icon": "ri-database-2-line", "category": "infrastructure", "sub": "influxdb.local", "description": "Time-series database backing observability, automation, and dashboard workloads.", "tags": ["timeseries", "database", "metrics"], "links": [{ "label": "Local", "url": "http://influxdb.local" }], "healthCheckUrl": "http://192.168.1.13:8087" }, + + { "name": "Synology DSM", "url": "http://nas.local", "icon": "ri-server-line", "category": "nas", "sub": "192.168.1.22", "description": "Primary NAS administration console for storage, shares, and system management.", "tags": ["nas", "storage", "synology"], "healthCheckUrl": "http://nas.local" }, + { "name": "Portainer", "url": "http://portainer.local", "icon": "ri-ship-line", "category": "nas", "sub": "portainer.local", "description": "Container administration UI for stack management and host-level operations.", "tags": ["containers", "admin", "nas"], "healthCheckUrl": "http://portainer.local" }, + { "name": "MinIO", "url": "http://minio.local", "icon": "ri-database-line", "category": "nas", "sub": "minio.local", "description": "S3-compatible object storage for backups, assets, and internal application data.", "tags": ["storage", "s3", "object"], "healthCheckUrl": "http://minio.local" }, + + { "name": "AdGuard", "url": "http://192.168.1.240/adguard", "icon": "ri-shield-check-line", "category": "k3s-services", "sub": "192.168.1.240/adguard", "description": "DNS filtering and network ad-blocking service exposed through the K3S cluster.", "tags": ["dns", "network", "k3s"], "healthCheckUrl": "http://192.168.1.240/adguard" }, + { "name": "Authentik", "url": "http://auth.local", "icon": "ri-login-box-fill", "category": "k3s-services", "sub": "auth.hasanbasic.dk", "description": "Identity provider handling SSO, application access, and login flows.", "tags": ["auth", "sso", "identity"], "links": [{ "label": "Public", "url": "https://auth.hasanbasic.dk" }], "healthCheckUrl": "http://auth.local" }, + { "name": "Vaultwarden", "url": "https://vault.hasanbasic.dk", "icon": "ri-lock-password-line", "category": "k3s-services", "sub": "vault.hasanbasic.dk", "description": "Password vault and secrets interface for secure credential storage.", "tags": ["security", "passwords", "vault"], "healthCheckUrl": "https://vault.hasanbasic.dk" }, + { "name": "Immich", "url": "https://immich.hasanbasic.dk", "icon": "ri-image-line", "category": "k3s-services", "sub": "immich.hasanbasic.dk", "description": "Photo and video backup platform for personal media organization.", "tags": ["photos", "media", "backup"], "healthCheckUrl": "https://immich.hasanbasic.dk" }, + + { "name": "Grafana", "url": "http://grafana.local", "icon": "ri-dashboard-line", "category": "k3s-monitoring", "sub": "grafana.local", "description": "Cluster dashboards for infrastructure health, logs, and service telemetry.", "tags": ["grafana", "monitoring", "k3s"], "healthCheckUrl": "http://grafana.local" }, + { "name": "Prometheus", "url": "http://prometheus.local", "icon": "ri-timer-flash-line", "category": "k3s-monitoring", "sub": "prometheus.local", "description": "Metrics collection and query engine for cluster and workload observability.", "tags": ["metrics", "prometheus", "k3s"], "healthCheckUrl": "http://prometheus.local" }, + { "name": "AlertManager", "url": "http://alertmanager.local", "icon": "ri-alarm-warning-line", "category": "k3s-monitoring", "sub": "alertmanager.local", "description": "Alert routing and notification management for Prometheus-driven incidents.", "tags": ["alerts", "prometheus", "ops"], "healthCheckUrl": "http://alertmanager.local" }, + { "name": "Loki", "url": "http://loki.local", "icon": "ri-file-search-line", "category": "k3s-monitoring", "sub": "loki.local", "description": "Centralized log storage and query service for applications and infrastructure.", "tags": ["logs", "observability", "k3s"], "healthCheckUrl": "http://loki.local" } ], "statusMonitors": [ {