diff --git a/public/icons/adguard-home.svg b/public/icons/adguard-home.svg new file mode 100644 index 0000000..7e3c634 --- /dev/null +++ b/public/icons/adguard-home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/docker.svg b/public/icons/docker.svg new file mode 100644 index 0000000..0903acd --- /dev/null +++ b/public/icons/docker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/filebrowser.svg b/public/icons/filebrowser.svg new file mode 100644 index 0000000..5c7a36e --- /dev/null +++ b/public/icons/filebrowser.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/freenas.svg b/public/icons/freenas.svg new file mode 100644 index 0000000..7e518c7 --- /dev/null +++ b/public/icons/freenas.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/gitea.svg b/public/icons/gitea.svg new file mode 100644 index 0000000..07505e4 --- /dev/null +++ b/public/icons/gitea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/grafana.svg b/public/icons/grafana.svg new file mode 100644 index 0000000..5b8e59c --- /dev/null +++ b/public/icons/grafana.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/home-assistant.svg b/public/icons/home-assistant.svg new file mode 100644 index 0000000..f6d0660 --- /dev/null +++ b/public/icons/home-assistant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/immich.svg b/public/icons/immich.svg new file mode 100644 index 0000000..5c758ff --- /dev/null +++ b/public/icons/immich.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/jellyfin.svg b/public/icons/jellyfin.svg new file mode 100644 index 0000000..c62f104 --- /dev/null +++ b/public/icons/jellyfin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/nextcloud.svg b/public/icons/nextcloud.svg new file mode 100644 index 0000000..841b9d9 --- /dev/null +++ b/public/icons/nextcloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/paperless-ngx.svg b/public/icons/paperless-ngx.svg new file mode 100644 index 0000000..cdf02c8 --- /dev/null +++ b/public/icons/paperless-ngx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/photoprism.svg b/public/icons/photoprism.svg new file mode 100644 index 0000000..1b60ff8 --- /dev/null +++ b/public/icons/photoprism.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/pi-hole.svg b/public/icons/pi-hole.svg new file mode 100644 index 0000000..5b023fc --- /dev/null +++ b/public/icons/pi-hole.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/plex.svg b/public/icons/plex.svg new file mode 100644 index 0000000..ae62ba7 --- /dev/null +++ b/public/icons/plex.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/portainer.svg b/public/icons/portainer.svg new file mode 100644 index 0000000..cc67715 --- /dev/null +++ b/public/icons/portainer.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/prometheus.svg b/public/icons/prometheus.svg new file mode 100644 index 0000000..da19fd6 --- /dev/null +++ b/public/icons/prometheus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/qbittorrent.svg b/public/icons/qbittorrent.svg new file mode 100644 index 0000000..e865b54 --- /dev/null +++ b/public/icons/qbittorrent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/syncthing.svg b/public/icons/syncthing.svg new file mode 100644 index 0000000..e3dbbba --- /dev/null +++ b/public/icons/syncthing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/tailscale.svg b/public/icons/tailscale.svg new file mode 100644 index 0000000..b7d6e8a --- /dev/null +++ b/public/icons/tailscale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/transmission.svg b/public/icons/transmission.svg new file mode 100644 index 0000000..04560ee --- /dev/null +++ b/public/icons/transmission.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/truenas.svg b/public/icons/truenas.svg new file mode 100644 index 0000000..a24865d --- /dev/null +++ b/public/icons/truenas.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/uptime-kuma.svg b/public/icons/uptime-kuma.svg new file mode 100644 index 0000000..3bf6fa6 --- /dev/null +++ b/public/icons/uptime-kuma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/vaultwarden.svg b/public/icons/vaultwarden.svg new file mode 100644 index 0000000..f4c992c --- /dev/null +++ b/public/icons/vaultwarden.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/wireguard.svg b/public/icons/wireguard.svg new file mode 100644 index 0000000..1de4ec6 --- /dev/null +++ b/public/icons/wireguard.svg @@ -0,0 +1,30 @@ + + + + + + wireguard + + + + diff --git a/public/screenshots/app-store.png b/public/screenshots/app-store.png index fb1a1c7..b69e7e0 100644 Binary files a/public/screenshots/app-store.png and b/public/screenshots/app-store.png differ diff --git a/public/screenshots/dashboard-overview.png b/public/screenshots/dashboard-overview.png index e75a722..4898f07 100644 Binary files a/public/screenshots/dashboard-overview.png and b/public/screenshots/dashboard-overview.png differ diff --git a/public/screenshots/gitops-sync.png b/public/screenshots/gitops-sync.png index 23ab18f..3130854 100644 Binary files a/public/screenshots/gitops-sync.png and b/public/screenshots/gitops-sync.png differ diff --git a/public/screenshots/migration-wizard.png b/public/screenshots/migration-wizard.png index b22a38e..2f55e6d 100644 Binary files a/public/screenshots/migration-wizard.png and b/public/screenshots/migration-wizard.png differ diff --git a/public/screenshots/perses-analytics.png b/public/screenshots/perses-analytics.png index 39a411c..c0afc0c 100644 Binary files a/public/screenshots/perses-analytics.png and b/public/screenshots/perses-analytics.png differ diff --git a/public/screenshots/shares-management.png b/public/screenshots/shares-management.png index ec4cbe6..f7aa83e 100644 Binary files a/public/screenshots/shares-management.png and b/public/screenshots/shares-management.png differ diff --git a/public/screenshots/system-logs.png b/public/screenshots/system-logs.png index 068064c..12f2461 100644 Binary files a/public/screenshots/system-logs.png and b/public/screenshots/system-logs.png differ diff --git a/src/api/client.ts b/src/api/client.ts index edabb10..1b6adab 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -164,11 +164,19 @@ function getFallbackData(endpoint: string, options?: RequestInit): any { if (endpoint.startsWith('/apps/catalog')) { return { catalog: [ - { id: 'immich', name: 'Immich Photo Hub', category: 'Media', description: 'Self-hosted photo and video backup with Intel QuickSync / OpenCL ML acceleration.', icon: 'Image', defaultPort: 2283, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, - { id: 'nextcloud', name: 'Nextcloud Hub', category: 'Productivity', description: 'Productivity platform for files, documents, and calendars.', icon: 'Cloud', defaultPort: 8080, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'docker' }, - { id: 'jellyfin', name: 'Jellyfin Media Server', category: 'Media', description: 'Media streaming system with hardware transcoding.', icon: 'Film', defaultPort: 8096, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'docker' }, - { id: 'paperless', name: 'Paperless-ngx', category: 'Productivity', description: 'Document archiving system with automated OCR & scanner ingestion.', icon: 'FileText', defaultPort: 28981, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, - { id: 'vaultwarden', name: 'Vaultwarden', category: 'Security', description: 'Lightweight Bitwarden password vault.', icon: 'Lock', defaultPort: 8222, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, + { id: 'immich', name: 'Immich Photo Hub', category: 'Media', description: 'Self-hosted photo and video backup with Intel QuickSync / OpenCL ML acceleration.', icon: 'immich', defaultPort: 2283, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, + { id: 'jellyfin', name: 'Jellyfin Media Server', category: 'Media', description: '4K HDR media streaming system with hardware transcoding.', icon: 'jellyfin', defaultPort: 8096, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'docker' }, + { id: 'nextcloud', name: 'Nextcloud Hub', category: 'Productivity', description: 'Productivity platform for files, documents, and calendars.', icon: 'nextcloud', defaultPort: 8080, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'docker' }, + { id: 'paperless', name: 'Paperless-ngx', category: 'Productivity', description: 'Document archiving system with automated OCR & scanner ingestion.', icon: 'paperless-ngx', defaultPort: 28981, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, + { id: 'vaultwarden', name: 'Vaultwarden', category: 'Security', description: 'Lightweight Bitwarden-compatible password vault in Rust.', icon: 'vaultwarden', defaultPort: 8222, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, + { id: 'home-assistant', name: 'Home Assistant Core', category: 'Automation', description: 'Local home automation and smart device controller.', icon: 'home-assistant', defaultPort: 8123, supportedRuntimes: ['docker', 'k3s'], recommendedRuntime: 'docker' }, + { id: 'plex', name: 'Plex Media Server', category: 'Media', description: 'Organize personal video, audio, and photo collections.', icon: 'plex', defaultPort: 32400, supportedRuntimes: ['docker'], recommendedRuntime: 'docker' }, + { id: 'qbittorrent', name: 'qBittorrent Web', category: 'Network', description: 'Fast, headless BitTorrent client with feature-rich web management.', icon: 'qbittorrent', defaultPort: 8085, supportedRuntimes: ['docker', 'systemd'], recommendedRuntime: 'docker' }, + { id: 'tailscale', name: 'Tailscale Mesh VPN', category: 'Network', description: 'Zero-config peer-to-peer VPN connecting nodes across networks.', icon: 'tailscale', defaultPort: 41641, supportedRuntimes: ['systemd'], recommendedRuntime: 'systemd' }, + { id: 'syncthing', name: 'Syncthing P2P', category: 'Productivity', description: 'Continuous decentralized file synchronization between devices.', icon: 'syncthing', defaultPort: 8384, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, + { id: 'uptime-kuma', name: 'Uptime Kuma', category: 'Monitoring', description: 'Self-hosted monitoring tool for HTTP, TCP, and ping healthchecks.', icon: 'uptime-kuma', defaultPort: 3001, supportedRuntimes: ['docker'], recommendedRuntime: 'docker' }, + { id: 'adguard-home', name: 'AdGuard Home', category: 'Network', description: 'Network-wide software for blocking ads & tracking via DNS.', icon: 'adguard-home', defaultPort: 3000, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, + { id: 'filebrowser', name: 'FileBrowser Quantum', category: 'Storage', description: 'Web-based file manager with upload, download, and instant preview.', icon: 'filebrowser', defaultPort: 8082, supportedRuntimes: ['systemd', 'docker'], recommendedRuntime: 'systemd' }, ], }; } diff --git a/src/components/AppIcon.tsx b/src/components/AppIcon.tsx new file mode 100644 index 0000000..ec566c6 --- /dev/null +++ b/src/components/AppIcon.tsx @@ -0,0 +1,124 @@ +import React, { useState } from 'react'; +import { + Layers, + Image as ImageIcon, + Cloud, + Film, + FileText, + Lock, + Home, + Shield, + Activity, + FolderSync, +} from 'lucide-react'; + +interface AppIconProps { + name: string; + className?: string; + alt?: string; +} + +const ICON_MAP: Record = { + immich: 'immich', + image: 'immich', + nextcloud: 'nextcloud', + cloud: 'nextcloud', + jellyfin: 'jellyfin', + film: 'jellyfin', + paperless: 'paperless-ngx', + 'paperless-ngx': 'paperless-ngx', + filetext: 'paperless-ngx', + vaultwarden: 'vaultwarden', + bitwarden: 'vaultwarden', + lock: 'vaultwarden', + 'home-assistant': 'home-assistant', + homeassistant: 'home-assistant', + home: 'home-assistant', + plex: 'plex', + qbittorrent: 'qbittorrent', + transmission: 'transmission', + tailscale: 'tailscale', + wireguard: 'wireguard', + syncthing: 'syncthing', + gitea: 'gitea', + prometheus: 'prometheus', + grafana: 'grafana', + perses: 'grafana', + portainer: 'portainer', + docker: 'docker', + 'adguard-home': 'adguard-home', + adguard: 'adguard-home', + 'pi-hole': 'pi-hole', + pihole: 'pi-hole', + 'uptime-kuma': 'uptime-kuma', + uptimekuma: 'uptime-kuma', + photoprism: 'photoprism', + filebrowser: 'filebrowser', + truenas: 'truenas', + freenas: 'freenas', +}; + +export const AppIcon: React.FC = ({ name, className = 'h-7 w-7', alt }) => { + const [hasError, setHasError] = useState(false); + const [useCdn, setUseCdn] = useState(false); + + const cleanKey = (name || '').toLowerCase().trim(); + const iconFileName = ICON_MAP[cleanKey] || cleanKey; + + const localSrc = `/icons/${iconFileName}.svg`; + const cdnSrc = `https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/${iconFileName}.svg`; + + const handleError = () => { + if (!useCdn) { + setUseCdn(true); + } else { + setHasError(true); + } + }; + + if (hasError) { + switch (cleanKey) { + case 'immich': + case 'image': + return ; + case 'nextcloud': + case 'cloud': + return ; + case 'jellyfin': + case 'film': + return ; + case 'paperless': + case 'paperless-ngx': + case 'filetext': + return ; + case 'vaultwarden': + case 'lock': + return ; + case 'home-assistant': + case 'homeassistant': + case 'home': + return ; + case 'tailscale': + case 'wireguard': + return ; + case 'perses': + case 'prometheus': + case 'grafana': + return ; + case 'syncthing': + return ; + default: + return ; + } + } + + return ( + {alt + ); +}; diff --git a/src/types/index.ts b/src/types/index.ts index 72d90cd..8a87c14 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -63,7 +63,7 @@ export type AppRuntime = 'systemd' | 'docker' | 'k3s'; export interface AppCatalogItem { id: string; name: string; - category: 'Media' | 'Storage' | 'Productivity' | 'Security' | 'Automation'; + category: 'Media' | 'Storage' | 'Productivity' | 'Security' | 'Automation' | 'Network' | 'Monitoring'; description: string; icon: string; defaultPort: number; diff --git a/src/views/AppsView.tsx b/src/views/AppsView.tsx index 97169eb..4ce8f06 100644 --- a/src/views/AppsView.tsx +++ b/src/views/AppsView.tsx @@ -15,6 +15,7 @@ import { Home, CheckCircle2, } from 'lucide-react'; +import { AppIcon } from '../components/AppIcon.js'; import type { AppCatalogItem, InstalledApp, AppRuntime } from '../types/index.js'; interface AppsViewProps { @@ -42,17 +43,6 @@ export const AppsView: React.FC = ({ vaultwarden: 'systemd', }); - const getIcon = (iconName: string) => { - switch (iconName) { - case 'Image': return ; - case 'Cloud': return ; - case 'Film': return ; - case 'FileText': return ; - case 'Lock': return ; - default: return ; - } - }; - const handleInstall = async (appId: string) => { setInstallingId(appId); try { @@ -109,8 +99,8 @@ export const AppsView: React.FC = ({ >
-
- {getIcon(item.icon)} +
+
{item.category} @@ -180,8 +170,8 @@ export const AppsView: React.FC = ({ {installedApps.map((app) => (
-
- +
+
diff --git a/src/views/OverviewView.tsx b/src/views/OverviewView.tsx index c83cebb..b7b0916 100644 --- a/src/views/OverviewView.tsx +++ b/src/views/OverviewView.tsx @@ -9,6 +9,7 @@ import { ArrowUpRight, TrendingUp, } from 'lucide-react'; +import { AppIcon } from '../components/AppIcon.js'; import type { SystemStatus, ZfsPool, InstalledApp } from '../types/index.js'; interface OverviewViewProps { @@ -112,9 +113,20 @@ export const OverviewView: React.FC = ({
{apps.length || 2} Active Services
-

- Immich (Systemd), Jellyfin (Docker) -

+
+
+ +
+
+ +
+
+ +
+
+ +
+
Runtime Engine: Docker + Systemd @@ -141,6 +153,156 @@ export const OverviewView: React.FC = ({
+ {/* Homarr-Style Active Services & Workloads Hub */} +
+
+
+ +

Appliance Services & Workloads

+ + Homarr Dashboard + +
+ +
+ +
+ {/* Workload 1: Immich Photo Hub */} +
+
+
+
+ +
+
+ + ONLINE +
+
+

Immich Photo Hub

+

+ Hardware-accelerated photo & video backup with Intel QuickSync ML +

+
+
+ + :2283 + + + Launch + + +
+
+ + {/* Workload 2: Jellyfin Media Server */} +
+
+
+
+ +
+
+ + ONLINE +
+
+

Jellyfin Media Server

+

+ 4K HDR media streaming with hardware transcoding & DLNA +

+
+
+ + :8096 + + + Launch + + +
+
+ + {/* Workload 3: CNCF Perses Analytics */} +
+
+
+
+ +
+
+ + ONLINE +
+
+

CNCF Perses Telemetry

+

+ Declarative visualization for ZFS I/O, ARC caching, and host metrics +

+
+
+ + :8080 + + +
+
+ + {/* Workload 4: Nextcloud Hub */} +
+
+
+
+ +
+
+ + READY +
+
+

Nextcloud Hub

+

+ Collaborative office, encrypted file sync, and calendar sharing +

+
+
+ + Catalog + + +
+
+
+
+ {/* Embedded Native Perses Analytics Panel */}