# Services Homepage

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

![License](https://img.shields.io/badge/license-MIT-blue.svg)
![Docker](https://img.shields.io/badge/docker-required-blue.svg)

## 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

```bash
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
<?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
<?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

```bash
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

```xml
<!-- 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:

```xml
<!-- 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:

```xml
<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`:
```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:
```css
.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`:

```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:
```css
.card::before {
  animation: shimmer 8s infinite linear;  /* Change 8s to adjust speed */
}
```

### Layout

Change grid responsiveness in `styles.css`:
```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

```bash
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

```bash
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:
```bash
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:
```bash
xmllint --noout services.xml
```

2. Check browser console for errors (F12)

3. Verify file permissions:
```bash
chmod 644 services.xml index.html styles.css
chmod 755 logos/
```

### Icons Not Displaying

1. Verify icon exists:
```bash
ls -lh logos/youricon.svg
```

2. Check icon reference in `services.xml` matches filename exactly

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

### Container Issues

```bash
# 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:

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

### Adding Authentication

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

```nginx
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:

```xml
<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](https://simpleicons.org/) (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