Compare commits

...

1 Commits

Author SHA1 Message Date
4bb9d9b123 UPDATE 2026-03-23 20:32:51 +01:00
3 changed files with 295 additions and 73 deletions

View File

@@ -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;
}

View File

@@ -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 = `<i class="${svc.icon}"></i><span>${svc.name}</span>`;
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);
});

View File

@@ -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": [
{