6.8 KiB
Unified Dashboard & External Access Strategy
"One page to rule them all" - Unified interface for tower-of-joy services Created: 2025-11-11
Overview
This document defines the strategy for creating a unified web interface that provides access to all tower-of-joy services through a single page with tabbed navigation.
Solution: Organizr + Nginx Proxy Manager
Organizr provides the unified tabbed interface NPM provides secure external access with SSL
Architecture
Internet
↓
[DNS: home.schweitz.net]
↓
[Router: Port Forward 443 → 192.168.86.149:443]
↓
[Nginx Proxy Manager: 443]
↓
[Organizr: 9999] ←→ [Service Tabs via iframe]
├── Portainer (8001)
├── Uptime Kuma (3001)
├── Netdata (19999)
├── Heimdall (8888)
└── More services...
URL Pattern: Single Domain Approach
Recommended Pattern:
https://home.schweitz.net → Organizr unified interface
All services accessed through Organizr tabs:
- Click "Portainer" tab → loads in iframe
- Click "Netdata" tab → loads in iframe
- Click "Uptime Kuma" tab → loads in iframe
Why this pattern?
- ✅ True "one page" experience
- ✅ Single SSL certificate
- ✅ Single URL to remember
- ✅ Centralized authentication
- ✅ Simple to maintain
Alternative: Hybrid Subdomain Pattern
If some services need direct access (bypassing Organizr):
https://home.schweitz.net → Organizr (main interface)
https://portainer.home.schweitz.net → Direct Portainer access
https://netdata.home.schweitz.net → Direct Netdata access
Requires:
- Wildcard DNS:
*.home.schweitz.net → 192.168.86.149 - Wildcard SSL cert OR individual certs per subdomain
Service Configuration in Organizr
Infrastructure Services (Primary Tabs)
| Service | Internal URL | Tab Name | Notes |
|---|---|---|---|
| Portainer | http://192.168.86.149:8001 | Portainer | Container management |
| Uptime Kuma | http://192.168.86.149:3001 | Uptime | Service monitoring |
| Netdata | http://192.168.86.149:19999 | Metrics | System metrics |
| Heimdall | http://192.168.86.149:8888 | Dashboard | Alternative launcher |
Optional Services (Additional Tabs)
| Service | Internal URL | Tab Name | Expose? |
|---|---|---|---|
| NPM Admin | http://192.168.86.149:81 | NPM | Admin only - local access |
| Headscale | http://192.168.86.149:8085 | VPN | Admin only |
| Ollama | http://192.168.86.149:11434 | AI | API only, no UI |
Future Application Services
| Service | Internal URL | Tab Name | Notes |
|---|---|---|---|
| Jellyfin | http://192.168.86.149:8096 | Media | GPU transcoding |
| Nextcloud | http://192.168.86.149:8082 | Cloud | File storage |
Iframe Embedding Challenges
Known Issues
Some services block iframe embedding via X-Frame-Options header:
- Netdata: Can be configured to allow embedding
- Portainer: May require configuration
- Uptime Kuma: Generally works fine
Solutions
Option 1: Configure services to allow embedding Add to docker-compose environment:
environment:
- X_FRAME_OPTIONS=SAMEORIGIN # Allow same-origin iframes
Option 2: NPM header manipulation Configure NPM to strip/modify headers for internal access
Option 3: Organizr "direct link" mode Services that don't work in iframes can open in new tab
Security Layers
Level 1: External Access (NPM)
- HTTPS with Let's Encrypt SSL
- External port 443 only
- DDoS protection via Cloudflare (optional)
Level 2: Application Authentication (Organizr)
- User authentication in Organizr
- Role-based access control
- SSO integration (optional)
Level 3: Service-Level Authentication
- Each service keeps its own auth
- Organizr can pass auth tokens (for supported services)
Level 4: Network Security (Headscale)
- VPN access for sensitive admin tools
- Public: Jellyfin, Nextcloud
- Private (VPN only): Portainer, NPM, Netdata
Implementation Steps
Phase 1: Deploy Organizr
make deploy-organizr
Phase 2: Configure Organizr
- Access http://192.168.86.149:9999
- Complete setup wizard
- Create admin user
- Add tabs for each service
Phase 3: Configure NPM for External Access
- Access NPM admin: http://192.168.86.149:81
- Add proxy host:
- Domain:
home.schweitz.net - Forward to:
192.168.86.149:9999 - Enable SSL with Let's Encrypt
- Force HTTPS redirect
- Domain:
Phase 4: Configure Router Port Forwarding
External Port 443 → Internal 192.168.86.149:443 (NPM HTTPS)
External Port 80 → Internal 192.168.86.149:80 (NPM HTTP redirect)
Phase 5: DNS Configuration
Point home.schweitz.net to your public IP
Phase 6: Test & Secure
- Test external access: https://home.schweitz.net
- Verify SSL certificate
- Test all service tabs
- Configure Organizr authentication
- Review security settings
Service Tab Recommendations
Homepage Tab
- Quick status dashboard
- Links to most-used services
- System health indicators
Essential Tabs (Always Visible)
- Portainer (container management)
- Uptime Kuma (monitoring)
- Netdata (metrics)
Application Tabs (After deployment)
- Jellyfin (media)
- Nextcloud (files)
Admin Tabs (Restricted)
- NPM (reverse proxy config)
- Headscale (VPN management)
Maintenance
Adding New Services
- Deploy service via Portainer/Docker Compose
- Add tab in Organizr settings
- Test iframe embedding
- Update this documentation
SSL Certificate Renewal
- Automatic via Let's Encrypt (NPM handles this)
- Check NPM dashboard for expiry dates
Security Updates
- Watchtower auto-updates containers (Phase 4)
- Review Organizr user access monthly
Troubleshooting
Service won't load in iframe
Problem: X-Frame-Options header blocking
Solution: Configure service to allow embedding, or use "open in new tab" mode
External access not working
Check:
- Router port forwarding configured (443 → 192.168.86.149:443)
- DNS pointing to correct public IP
- NPM proxy host configured correctly
- SSL certificate generated successfully
Authentication issues
Check:
- Organizr user permissions
- Service-specific authentication (each service has own login)
- Consider implementing SSO for seamless experience
Future Enhancements
Potential Upgrades
- Authelia: Centralized authentication with 2FA
- Cloudflare Tunnel: Avoid port forwarding entirely
- Custom Theme: Brand Organizr to match preferences
- API Integration: Show live stats in Organizr homepage
Next Steps:
- Deploy Organizr:
make deploy-organizr - Configure tabs for existing services
- Set up NPM proxy for external access
- Test the unified interface