Files
Homepage/README.md

14 KiB
Raw Permalink Blame History

Services Homepage

A lightweight, self-hosted dashboard for quick access to your Docker services with a modern holographic UI design.

License Docker

Features

  • 🎨 Holographic Glass Design - Modern liquid glass buttons with animated shimmer effects
  • 🎯 Dynamic Service Loading - Services configured via simple XML file
  • 🔍 Smart URL Resolution - Automatic handling of ports, protocols, and hostnames
  • 🎭 Icon Integration - Includes Simple Icons pack (3000+ brand logos)
  • 📱 Responsive Layout - Works seamlessly on desktop and mobile
  • 🐳 Docker-Ready - Single-container deployment with nginx
  • Lightweight - Minimal resources, fast loading
  • 🔎 Search & Filter - Instant search with keyboard shortcut (press /)
  • Connection Details - Info button shows hostname/port for each service
  • 🎮 Keyboard Navigation - Arrow keys to navigate, Enter to open, Esc to clear
  • 🟢 Status Indicators - Optional visual status (online/offline/maintenance)
  • 🏥 Automatic Health Checks - Real-time ping tests to detect service availability
  • 📂 Service Groups - Organize services into categorized sections

Quick Start

1. Deploy with Docker Compose

docker-compose up -d

The homepage will be available at http://localhost:8088

2. Configure Your Services

Edit services.xml to add your services organized into groups:

<?xml version="1.0" encoding="UTF-8"?>
<services>
  <group name="Management">
    <service 
      name="Portainer" 
      proto="https" 
      port="9443"
      logo="portainer.svg" 
    />
  </group>
  
  <group name="Media">
    <service 
      name="Jellyfin" 
      proto="http" 
      port="8096" 
      host="jellyfin.example.com"
      logo="jellyfin.svg" 
    />
  </group>
</services>

Or use services without groups (they'll appear in a single grid):

<?xml version="1.0" encoding="UTF-8"?>
<services>
  <service name="Portainer" proto="https" port="9443" logo="portainer.svg" />
  <service name="Jellyfin" proto="http" port="8096" logo="jellyfin.svg" />
</services>

3. Restart to Apply Changes

docker-compose restart

Configuration Guide

Service Attributes

Attribute Required Description Example
name Yes Display name for the service "Nextcloud"
proto No Protocol (http/https) "https" (default: "http")
port No Port number "8080"
host No Custom hostname or full URL "nextcloud.example.com"
logo No Icon filename in /logos/ "nextcloud.svg"
status No Set to "maintenance" to skip health check "maintenance"
check-health No Enable/disable auto health check "true" (default) or "false"

URL Resolution Logic

The service URL is built using this priority:

  1. Full URL - If host starts with http:// or https://, use as-is
  2. Hostname with Port - If host contains :port, use proto://host:port
  3. Hostname Only - If host is set (no port), use https://host (ignores proto and port)
  4. Fallback - Use current page hostname with specified proto and port

Examples

<!-- Full URL (ignores proto and port) -->
<service name="Example" host="https://example.com/app" />

<!-- Public domain (defaults to HTTPS) -->
<service name="Nextcloud" host="cloud.example.com" logo="nextcloud.svg" />

<!-- Custom port on domain -->
<service name="Jellyfin" host="media.example.com:8096" proto="http" logo="jellyfin.svg" />

<!-- Local service (uses page hostname) -->
<service name="Portainer" proto="https" port="9443" logo="portainer.svg" />

<!-- Service with status indicator -->
<service name="Home Assistant" proto="http" port="8123" logo="homeassistant.svg" status="online" />

Keyboard Shortcuts

  • / - Focus the search bar (press from anywhere)
  • Arrow Keys - Navigate between service cards (Up/Down/Left/Right)
  • Enter - Open the selected service in a new tab
  • Esc - Clear the current selection

Info Button

When a service has both a hostname and port configured, a small info button (ⓘ) appears in the bottom-right corner of the card. Click it to view connection details including:

  • Service name
  • Hostname
  • Port number
  • Protocol

Status Indicators

Services are automatically checked for availability when the page loads. Status indicators appear in the top-right corner of each card:

<!-- Automatic health check (default behavior) -->
<service name="Jellyfin" proto="http" port="8096" logo="jellyfin.svg" />

<!-- Manual maintenance mode (skips health check) -->
<service name="Nextcloud" status="maintenance" host="cloud.example.com" logo="nextcloud.svg" />

<!-- Disable health check for a service -->
<service name="Legacy App" check-health="false" proto="http" port="8080" logo="app.svg" />

Status colors:

  • Gray spinning (checking) - Currently testing service availability
  • Green pulsing (online) - Service responded successfully to health check
  • Red (offline) - Service failed to respond or timed out (5 seconds)
  • Orange (maintenance) - Manual maintenance mode, health check skipped

How Health Checks Work

  • Each service is automatically pinged when the page loads
  • Uses a 5-second timeout per service
  • Checks run in parallel for all services
  • Services marked status="maintenance" skip the health check
  • Set check-health="false" to disable checking for specific services
  • No server-side component needed - runs entirely in the browser

Service Groups

Organize your services into categorized sections for better organization:

<services>
  <group name="Management">
    <service name="Portainer" proto="https" port="9443" logo="portainer.svg" />
    <service name="Uptime Kuma" proto="http" port="3001" logo="uptime-kuma.svg" />
  </group>

  <group name="Media">
    <service name="Jellyfin" proto="http" port="8096" logo="jellyfin.svg" />
    <service name="Transmission" proto="http" port="9091" logo="transmission.svg" />
  </group>

  <group name="Storage">
    <service name="Nextcloud" host="cloud.example.com" logo="nextcloud.svg" />
    <service name="FileBrowser" proto="http" port="8986" logo="filebrowser.svg" />
  </group>
</services>

Group Features

  • Categorization: Group related services together (Media, Management, Development, etc.)
  • Visual Separation: Each group has a styled header with an accent underline
  • Smart Search: Searching filters services and hides empty groups automatically
  • Backward Compatible: Services without groups still work (displayed in a single grid)
  • Flexible: Use as many or as few groups as needed

Group Tips

  • Use clear, descriptive group names (e.g., "Media Services" instead of "Group 1")
  • Keep related services together for easier navigation
  • Groups appear in the order defined in the XML
  • Empty groups (no services) are automatically hidden

Icon Management

Using Included Icons

The repository includes Simple Icons (3000+ brand logos). Available icons are in:

logos/simple-icons/icons/

Adding Custom Icons

  1. Add your SVG file to the logos/ directory
  2. Reference it in services.xml:
<service name="MyApp" logo="myapp.svg" />

Icon Styling

All icons are automatically styled white using CSS filters. To customize:

Edit styles.css and modify the .card .logo rule:

.card .logo {
  filter: brightness(0) invert(1);  /* White icons */
  /* OR */
  filter: hue-rotate(180deg);       /* Color shift */
}

Customization

Theme Colors

Edit CSS variables in styles.css:

:root {
  --bg: #0f1720;           /* Background color */
  --card: #0b1220;         /* Card background */
  --accent: #4f46e5;       /* Accent color (purple) */
  --muted: #94a3b8;        /* Muted text */
}

Holographic Effects

The holographic button effects include:

  • Shimmer Animation - Continuous light sweep (8s loop, 3s on hover)
  • Gradient Background - Purple/pink gradient blend
  • Glowing Border - Animated gradient border on hover
  • Backdrop Blur - Glass-like frosted effect

To adjust shimmer speed, modify the @keyframes shimmer animation:

.card::before {
  animation: shimmer 8s infinite linear;  /* Change 8s to adjust speed */
}

Layout

Change grid responsiveness in styles.css:

.grid {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  /* Adjust minmax() to change card width */
}

File Structure

services-homepage/
├── docker-compose.yml      # Docker deployment config
├── Dockerfile             # Custom nginx build (if needed)
├── index.html             # Main HTML page
├── styles.css             # Holographic UI styles
├── services.xml           # Service definitions
├── README.md              # This file
├── LICENSE                # License information
└── logos/                 # Icon directory
    ├── simple-icons/      # Simple Icons pack (3000+ logos)
    ├── default.svg        # Fallback icon
    ├── gitea.svg
    ├── jellyfin.svg
    ├── nextcloud.svg
    └── ...

Updating Icons

Replace All Icons with Simple Icons

cd logos
for f in *.svg; do
  basename=$(basename "$f" .svg)
  match=$(find simple-icons/icons -type f -iname "*${basename}*.svg" -print -quit)
  [ -n "$match" ] && cp "$match" "$f" && echo "Updated: $f"
done

Find Available Icons

ls logos/simple-icons/icons/ | grep -i "keyword"

Backup and Recovery

Icon backups are automatically created in:

logos/backup-YYYYMMDD-HHMMSS/

To restore from backup:

cp logos/backup-20251123-231406/*.svg logos/
docker-compose restart

Troubleshooting

Cloudflare, HTTPS and Mixed Content

If you're serving the homepage over HTTPS (for example, via Cloudflare), your browser will block active (programmatic) HTTP requests to local IPs — this is "mixed content". That can cause the health checks for local services to fail or to be marked offline.

Recommendations:

  • Enable HTTPS for your local services (e.g., configure TLS or use a reverse proxy with a valid certificate) and/or use Cloudflare Tunnel to serve the service with a domain and TLS.
  • Or configure a server-side proxy that performs health checks and serves the results over HTTPS (for example, add a proxy endpoint in your nginx config and proxy_pass to the local IP/port); the browser will then make a same-origin secure request to the proxy rather than directly to the IP.
  • In services.xml, use the tailscale-ip attribute to supply an easily-editable Tailscale IP for local services that should be used for links and health checks.
  • In services.xml, use the tailscale-ip attribute to supply an easily-editable Tailscale IP for local services that should be used for links and health checks.

Per-service local-ip override:

  • If a specific service has a local-ip attribute (for example, local-ip="192.168.2.180"), the server-side health proxy will use that local IP for the health check. This allows per-service control when the internal IP differs from the household-wide Tailscale IP or when services are bound to different hosts.

Cloudflare Insights & CORS:

  • If you see console errors about static.cloudflareinsights.com or messages like "CORS request did not succeed" or "integrity mismatch", this is likely a script injected by Cloudflare. This is not part of the homepage codebase and is injected by Cloudflare's edge. You can disable Cloudflare Analytics/Insights or adjust settings in the Cloudflare dashboard to remove or avoid that script if it's causing issues with CSP or integrity.

Services Not Loading

  1. Check services.xml syntax:
xmllint --noout services.xml
  1. Check browser console for errors (F12)

  2. Verify file permissions:

chmod 644 services.xml index.html styles.css
chmod 755 logos/

Icons Not Displaying

  1. Verify icon exists:
ls -lh logos/youricon.svg
  1. Check icon reference in services.xml matches filename exactly

  2. Clear browser cache (Ctrl+Shift+R)

Container Issues

# View logs
docker logs services-homepage

# Restart container
docker-compose restart

# Rebuild if files changed
docker-compose up -d --force-recreate

Performance

  • Image Size: ~45MB (nginx:alpine base)
  • Memory Usage: ~5-10MB
  • Load Time: <100ms (local network)
  • Icons: Cached by browser after first load

Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • Mobile browsers (iOS Safari, Chrome Android)

Security

  • All volumes mounted read-only (:ro)
  • No external dependencies at runtime
  • Logs automatically rotated (5MB max, 2 files)
  • CORS not enabled (same-origin only)

Advanced Usage

Custom Nginx Config

Create nginx.conf and mount it:

volumes:
  - ./nginx.conf:/etc/nginx/nginx.conf:ro

Adding Authentication

Use a reverse proxy (Nginx Proxy Manager, Caddy, Traefik) with basic auth:

location / {
  auth_basic "Services";
  auth_basic_user_file /etc/nginx/.htpasswd;
  proxy_pass http://services-homepage:80;
}

Dynamic Port Updates

For services with dynamic ports (e.g., Transmission behind VPN), use environment variables:

<service name="Transmission" proto="http" port="${TRANS_PORT}" logo="transmission.svg" />

Then update via script or use a template processor.

Contributing

Contributions welcome! Completed features:

  • Search/filter functionality with keyboard shortcut
  • Keyboard navigation (arrow keys, Enter, Esc)
  • Service status indicators (online/offline/maintenance)
  • Automatic health checks with ping tests
  • Info button showing connection details
  • Service groups/categories

Areas for future improvement:

  • Service health checks via custom endpoints
  • Drag-and-drop reordering within groups
  • Collapsible group sections
  • Custom themes/color schemes
  • Export/import service configurations
  • Dashboard widgets (time, weather, etc.)

License

MIT License - See LICENSE file for details

Credits

  • Icons: Simple Icons (CC0 1.0 Universal)
  • Design Inspiration: Holographic UI by vishnu137 on CodePen
  • Web Server: nginx Alpine

Support

For issues, questions, or suggestions:

  1. Check this README first
  2. Review browser console for errors
  3. Check Docker logs: docker logs services-homepage
  4. Verify services.xml syntax

Version: 1.0.0
Last Updated: November 23, 2025