All apps · 0 apps
Server-Manager-Portal
Docker app from jl94x4's Repository
Overview
Readme
View on GitHub
Server Portal
A premium, fully-automated management and analytics portal for Plex and Jellyfin media servers.
Built with Node.js · Express · React · Tailwind CSS
Server Portal is a self-hosted web application that turns your Plex or Jellyfin server into a fully managed streaming service. It handles everything from user onboarding and automated access management, to real-time analytics, live session monitoring, trending content discovery, and personalized wrap-ups for every user, all from one polished, mobile-first dashboard with a premium glass UI.
Once set up, users can sign in with Plex OAuth or Jellyfin authentication/Quick Connect to see their own portal, activity, and stats.
Feature Overview
Personal Analytics & Wrap-Up
Every user gets a rich, personalized dashboard packed with insights about their own streaming habits. A time-period filter (7 / 30 / 60 / 90 / 180 / 365 days / All Time) updates every card simultaneously. Metadata is cached server-side and refreshed by a background job every 30 minutes for near-instant filter switching.
| Card | What it shows |
|---|---|
| Server Rank | Your current rank on the server leaderboard, a progress bar showing your percentile, a mini-leaderboard showing the 2 users above/below you, and a "plays to climb" target to beat the person above you |
| Total Streams | Total play count with a visual breakdown by Movies / Episodes / Tracks (with %), daily average, unique titles, and a recent watch history list |
| Top Binge | Your most-watched TV show with its backdrop art, synopsis, and runner-up shows with posters |
| Top Movie | Your most-watched movie with its backdrop art, tagline, synopsis, release year, and runner-up movies |
| Media Profile | Your viewer personality type (Movie Buff, TV Show Binger, Music Lover, Mixed Bag) with colour-coded breakdown bars, percentage splits, and top 3 movies + top 3 shows |
| Watch Style | Discovery vs Rewatch analysis with a split progress bar and your top 5 most-rewatched titles |
| Streaming Habit | Weekday vs Weekend split bar chart, average plays per day, and habit label (Weekend Warrior, Weekday Streamer, Balanced) |
| Top Library | Your most-used media library with a full ranked breakdown of all libraries |
| Top Day | An animated bar chart showing your plays across all 7 days of the week, highlighting your peak day |
| Peak Hours | An animated hourly distribution chart showing what time of day you stream the most |
| Time of Day | Your streaming persona (Night Owl, Early Bird, Evening Streamer, Afternoon Watcher) with a contextual description |
All cards open into detailed modals loaded with contextual data, media artwork, and dynamic charts.
Shareable wrap-up - Export your personal wrap-up as a PNG image from the home dashboard. The share modal previews the real card grid and supports native share on supported devices, with download as a fallback.
Paginated watch history - Recently Watched and Your Most Watched use responsive pagination (18 items per page on desktop, 12 on mobile) so large libraries stay fast and readable.
Admin Dashboard
A comprehensive control panel for the server owner:
- Live Session Monitor - Real-time view of all active streams with user avatar, media title, progress bar, stream type badge (Direct Play / Transcode), and a click-through technical modal showing video codec, audio codec, bitrate, channels, resolution, and transcode reason
- User Management Table - View all users with their Plex or Jellyfin avatar, username, email, access expiry date, last seen timestamp, and quick-action buttons (+1 Month, +1 Year, Unlimited, Revoke)
- Server Leaderboard - Server-wide play count rankings across all time periods, updated automatically in the background
- Audit Log - Timestamped record of all system actions (access granted, revoked, extended, expired)
- Settings UI - Configure every aspect of the portal from the browser without touching config files
- Customizable Home Layout - Reorder home page sections and show or hide whole blocks (Personal Wrap-Up, Main grid, Recently / Most Watched, Recently Added) from Settings → Home Layout, with a live preview before saving. The main dashboard grid keeps a fixed balanced two-column layout so card heights stay aligned
- Library Maintenance - Scan libraries for missing or empty media, manage exclusions, and run cleanup tasks from the Maintenance page
Customizable Home Layout
Admins can tailor the home page for their community without editing code:
| Control | What it does |
|---|---|
| Section order | Drag and drop the four major home sections into any order |
| Section visibility | Toggle each section Shown or Hidden with one click |
| Live preview | See exactly how the layout will look before you save |
| Locked main grid | Left and right dashboard columns stay balanced; individual widget order inside the grid is fixed to prevent uneven card heights |
Layout settings are saved server-wide, validated on the backend, and applied to every user on the next page load. Admin-only widgets (Quick Actions, Server Admin badge) cannot be hidden through layout tampering.
Discover Page
A curated content discovery experience for all users, powered by server-wide watch history and live media activity:
Live activity
- Real-time stream summary cards (total streams, direct play, transcoding, bandwidth)
- Now playing cards with poster art, quality badges, player info, progress bar, and ETA
- Responsive layout: 3 stretched cards by default, up to 4 across on ultra-wide displays when 4 or more streams are active
- Activity refreshes every second while the page is open
Recently added
- Movies, TV shows, and music grids with poster quality badges
- 20 items per section on desktop and 12 on mobile by default
- 10-column poster grid on large screens
- Configurable limit dropdown (12, 20, 50, 100, 150, 200, 250 items) with preference saved in the browser
Trending and community picks
- Trending This Week - What the whole server has been watching in the last 7 days
- Top Movies / Top Shows - The most-played movies and shows over the past month
- Weekend Warriors - Content that spikes on Fridays, Saturdays, and Sundays
- Night Owl Club - Content most watched between midnight and 5am
- All-Time Greats - The highest play-count content ever on the server
- Cult Classics - Niche content with extremely high plays relative to its tiny viewer count
- Blast from the Past - Pre-2000 titles getting recent love
All discover items display server artwork, play counts, and quality badges (4K, HDR, AV1/HEVC, Atmos, and more). Trending and analytics caches are reused on startup when still fresh, so the portal loads quickly after restarts.
Media Stack
Browse your Sonarr and Radarr activity directly inside the portal:
- Release Calendar - Upcoming TV episodes and movie releases with poster art, air dates, and grabbed/missing status
- Active Queue - Live download queue from Sonarr and Radarr with progress and status
- Recent History - Import and grab history across both services
- Month Navigation - Browse releases by month with auto-advance to the next month that has content
Configure Sonarr/Radarr URLs and API keys in Settings → Media Stack.
User Onboarding & Access Management
- Invite Link System - Generate shareable invite links with a configurable max-use limit and custom duration. Users claim access via a branded landing page
- Plex OAuth - Secure login via official Plex.tv authentication. No Plex passwords stored
- Jellyfin Auth + Quick Connect - Jellyfin portals support username/password auth and one-click Quick Connect, with admin detection from Jellyfin policy
- Automated Temporary Access - Auto-grant configurable temporary access periods (e.g., 3 days) to all new users
- Access Expiry - Set hard expiry dates per user. The system automatically revokes portal access when time is up
- Inactivity Cleanup - Automatically remove users who haven't streamed in a configurable number of days, with per-user exemptions available
- Grace Period Notifications - Warn users via email before their access expires
Automated Communications
Beautiful, responsive HTML emails sent automatically:
| Email Type | Trigger |
|---|---|
| Welcome | Immediately when a user joins |
| Temporary Access Warning | When a temporary access user is approaching expiry |
| Access Expired | When a user's access is automatically removed |
| Inactivity Warning | Before an inactive user is purged |
| Weekly/Monthly Newsletter | Scheduled email featuring newly added Movies, TV Shows, and Music |
Public-Facing Pages
- Landing Page - A sleek login page showing live library stats (total movies, shows, music) and your configured server branding
- Status Page - A public
/statusdashboard showing the live uptime of your media server, request tools (Seerr/Jellyseerr/Ombi), analytics companion, and download clients - Invite Claim Page - A dedicated, shareable page for invited users to claim their account
Custom UI Themes
- Multiple Dark Themes - Users can select between Plex Dark (classic orange), Sleek Slate (modern blue), or Nordic Frost (cool indigo) directly from the navigation panel.
- Admin Configuration - Admins can set the default theme for new users and visitors from Settings ➔ Portal UI.
- Dynamic Accent Colors - Interface elements, charts, active navigation states, and borders dynamically update to match the selected theme's brand palette.
Mobile-First Design
- Full bottom navigation bar on mobile with smooth tab switching
- Clean top header on mobile showing only the server logo and essential actions
- All modals, cards, and charts are fully responsive and touch-friendly
- Safe area inset support for modern iOS and Android browsers
Technology Stack
| Layer | Technology |
|---|---|
| Backend | Node.js, Express.js |
| Frontend | React 18 (bundled via esbuild), TypeScript |
| Styling | Tailwind CSS v3 |
| Auth | JWT (httpOnly cookies) + Plex.tv OAuth or Jellyfin authentication |
| Data | Local JSON flat-files (no database required) |
| Nodemailer (compatible with any SMTP provider) | |
| Icons | Lucide React |
| Compression | GZIP via compression middleware |
Security
- No Plex Passwords - Plex authentication is handled by Plex.tv OAuth. Jellyfin password login is exchanged directly with your Jellyfin server and is not stored by the portal
- JWT Session Security - Cookies use
httpOnly,secure, andsameSite: laxflags to reduce XSS and CSRF risk while keeping auth redirects reliable - Rate Limiting - Authentication endpoints have strict rate limiting to prevent brute-force attacks
- HTTP Security Headers - HSTS, X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy, and Permissions-Policy enforced on every response
- Admin Protection - Admin routes require an authenticated admin session. Plex admins are verified from server ownership; Jellyfin admins are verified from Jellyfin user policy
- Reverse Proxy Ready - Supports Nginx, Caddy, and Cloudflare via
X-Forwarded-Proto/X-Forwarded-Forheader trust, including optional subpath hosting (e.g.https://media.example.com/portal) - Injection Proof - Uses a flat-file JSON system, making SQL injection structurally impossible
Getting Started
Prerequisites
- Node.js v20.6 or newer (for native
.envsupport) - A Plex Media Server with an admin Plex token, or a Jellyfin Server with an admin API key
- (Optional) An SMTP provider for email notifications
Installation
1. Clone the repository
git clone https://github.com/jl94x4/Server-Manager-Portal.git
cd Server-Manager-Portal
2. Install dependencies
npm install
3. Generate your JWT secret
printf 'JWT_SECRET=%s\n' "$(node -e "console.log(require('crypto').randomBytes(48).toString('hex'))")" > .env
4. Start the application
npm start
npm startautomatically builds the React frontend and Tailwind CSS, then starts the server on port 2121.
5. First-time admin setup
- Navigate to
http://localhost:2121 - Choose Plex or Jellyfin in the first-time setup wizard
- Plex setup uses Plex OAuth/token and server selection
- Jellyfin setup uses Jellyfin URL + API key, then supports Jellyfin login and Quick Connect
- Go to Settings in the sidebar to configure Media Player, SMTP, temporary access settings, branding, and scheduled tasks
Media player modes
| Mode | Authentication | Analytics companion | Branding |
|---|---|---|---|
| Plex | Plex.tv OAuth, Plex token, selected owned server | Tautulli | Plex or custom theme |
| Jellyfin | Jellyfin username/password or Quick Connect | Jellystat | Jellyfin server icon and splash screen proxy, or custom theme |
Plex mode keeps the original Plex OAuth and Tautulli flow. Jellyfin mode uses your Jellyfin URL/API key for user sync, session activity, Quick Connect, and server branding assets.
Docker Deployment
The recommended way to run Server Portal in production is Docker with a persistent volume for config/.
Pre-built images (GHCR)
Official images are published automatically on every push to main and beta:
| Tag | Branch | Image |
|---|---|---|
latest |
main |
ghcr.io/jl94x4/server-manager-portal:latest |
beta |
beta |
ghcr.io/jl94x4/server-manager-portal:beta |
Pull and run without building locally:
docker pull ghcr.io/jl94x4/server-manager-portal:latest
docker run -d \
--name server-manager-portal \
-p 2121:2121 \
-e JWT_SECRET="your-secret-at-least-32-chars" \
-e FORCE_SECURE_COOKIES=true \
-e PUBLIC_BASE_URL=https://portal.example.com \
-v "$(pwd)/config:/app/config" \
-v "$(pwd)/backup:/app/backup" \
ghcr.io/jl94x4/server-manager-portal:latest
Use the beta tag to test upcoming features before they land on latest.
Quick start (Docker Compose)
1. Clone and configure
git clone https://github.com/jl94x4/Server-Manager-Portal.git
cd Server-Manager-Portal
cp .env.example .env
Edit .env and set JWT_SECRET (at least 32 characters). If the portal is served over HTTPS behind a reverse proxy, also set:
FORCE_SECURE_COOKIES=true
PUBLIC_BASE_URL=https://portal.example.com
2. Build and run
docker compose up -d --build
The portal listens on port 2121 by default. Open http://localhost:2121 and complete the first-time setup for Plex or Jellyfin.
3. Persisted data
| Host path | Container path | Purpose |
|---|---|---|
./config |
/app/config |
All JSON settings, users, caches, logs |
./backup |
/app/backup |
Rolling backup snapshots |
On first startup, any legacy JSON files still in the project root are automatically migrated into config/.
Docker Compose tips
- Change the published port: set
PORT=8080in.env(maps host8080→ container2121). - Integrations on your LAN (Sonarr, Radarr, Tautulli, Jellystat, Seerr/Jellyseerr/Ombi): set
ALLOW_PRIVATE_INTEGRATION_URLS=trueand use reachable URLs from inside the container (e.g.http://host.docker.internal:8989on Docker Desktop, or your host IP on Linux). - View logs:
docker compose logs -f portal - Update:
git pull && docker compose up -d --build
Build the image manually
docker build -t server-manager-portal .
docker run -d \
--name server-manager-portal \
-p 2121:2121 \
-e JWT_SECRET="your-secret-at-least-32-chars" \
-e FORCE_SECURE_COOKIES=true \
-e PUBLIC_BASE_URL=https://portal.example.com \
-v "$(pwd)/config:/app/config" \
-v "$(pwd)/backup:/app/backup" \
server-manager-portal
Reverse proxy (Nginx / Caddy / Traefik)
Run the container on an internal port and proxy HTTPS to it.
Root hosting (recommended)
Example Caddy:
portal.example.com {
reverse_proxy localhost:2121
}
Set FORCE_SECURE_COOKIES=true and PUBLIC_BASE_URL=https://portal.example.com so session cookies and email links use the public URL.
Subpath hosting
You can also host the portal under a path on an existing domain, for example https://media.example.com/portal alongside Plex, Jellyfin, or other services.
Example Caddy:
media.example.com {
handle /portal/* {
reverse_proxy localhost:2121
}
}
Set these environment variables:
BASE_PATH=/portal
PUBLIC_BASE_URL=https://media.example.com/portal
FORCE_SECURE_COOKIES=true
BASE_PATH can be omitted if PUBLIC_BASE_URL already includes the path — the app derives it automatically. Leave both unset for root hosting; existing deployments are unchanged.
The proxy must forward requests with the /portal prefix intact (do not strip the path before the app). The portal rewrites asset and API URLs internally.
Unraid
Server Manager Portal is available in the Unraid Community Applications (CA) store!
Install via Community Applications (Recommended)
- Open the Apps tab in your Unraid dashboard
- Search for "Server Manager Portal"
- Click Install
- Set your JWT Secret and adjust appdata paths if needed (default:
/mnt/user/appdata/server-manager-portal/) - Click Apply and open the WebUI — you're done! 🎉
Manual Template Installation (Alternative)
If you prefer to install the template manually on Unraid 6+:
- Download the template file:
unraid/server-manager-portal.xml - Rename the file with a
my-prefix, e.g.my-server-manager-portal.xml - Upload it to your Unraid server at:
/boot/config/plugins/dockerMan/templates-user/ - Go to Docker → Add Container and select Server-Manager-Portal from the User Templates dropdown
- Set JWT Secret and adjust appdata paths (defaults:
/mnt/user/appdata/server-manager-portal/) - Apply and open the WebUI
The template uses ghcr.io/jl94x4/server-manager-portal:latest by default.
Environment variables
| Variable | Required | Description |
|---|---|---|
JWT_SECRET |
Yes | Session signing secret (min 32 characters) |
PORT |
No | Listen port inside the container (default 2121) |
BIND_HOST |
No | Bind address (default 0.0.0.0) |
CONFIG_DIR |
No | Runtime data directory (default /app/config in Docker) |
PUBLIC_BASE_URL |
Recommended | Public HTTPS URL for links and emails. Include the subpath when using one, e.g. https://media.example.com/portal |
BASE_PATH |
No | URL prefix when hosted under a subpath (e.g. /portal). Leave empty for root hosting |
FORCE_SECURE_COOKIES |
Recommended | Set true when behind HTTPS |
ALLOW_PRIVATE_INTEGRATION_URLS |
No | Allow LAN/private URLs for Arr stack integrations |
SETUP_TOKEN |
No | Token for remote first-time setup |
CLIENT_ID |
No | Fixed Plex OAuth client id (auto-generated if unset; Plex mode only) |
See .env.example for a full template.
Configuration
All configuration is managed through the Settings UI in the browser. Key options include:
| Setting | Description |
|---|---|
| Media Player | Choose Plex or Jellyfin |
| Plex Token / Server | Plex admin token, selected server, and optional direct Plex URL |
| Jellyfin URL / API Key | Jellyfin server URL and API key for users, sessions, Quick Connect, and branding proxy |
| Branding & UI | Portal accent colour, server logo, Jellyfin/Plex preset, and splash background |
| Temporary Access Duration | Number of days new users get for free |
| Inactivity Threshold | Days of inactivity before auto-removal |
| SMTP Settings | Host, port, username, password, from address |
| Newsletter Schedule | Weekly or monthly, with day/time selection |
| Home Layout | Section order and visibility for the user home page |
| Sonarr / Radarr URLs | For media stack calendar, queue, and history |
| Tautulli / Jellystat | Tautulli for Plex analytics, Jellystat for Jellyfin analytics |
| Status Page Services | Define services and their health check URLs |
Background Tasks
The Settings → Background Tasks page shows the active scheduler and lets admins run jobs manually. Task labels follow the selected media player:
| Task | Plex mode | Jellyfin mode |
|---|---|---|
| User sync | Sync Plex Users | Sync Jellyfin Users |
| Expiry checks | Email users nearing expiry | Same |
| Revoke access | Removes expired Plex access | Revokes expired portal access |
| Inactive cleanup | Revokes inactive users | Revokes inactive Jellyfin portal users |
| Analytics cache | Uses Plex/Tautulli data where configured | Uses Jellyfin/Jellystat data where configured |
| Library stats | Plex Stats Builder | Hidden in Jellyfin mode |
| Maintenance index | Builds media/request index for cleanup rules | Same |
| Auto rolling backup | Creates rolling config backups | Same |
The Settings → System diagnostics page uses the same media-aware task list so Jellyfin portals are not penalized for Plex-only jobs.
Project Structure
Server-Manager-Portal/
├── index.js # Backend: Express API, Plex/Jellyfin integrations, auth, email, background jobs
├── index.tsx # Frontend entry point
├── client/ # React application source
│ ├── App.tsx # App shell, routing, responsive layout
│ ├── screens.tsx # Dashboards, Discover, login, and shared screens
│ ├── home/ # User dashboard layout and widget renderers
│ ├── settings/ # Settings UI (Media Player, Home Layout, System, Background Tasks)
│ ├── shared/ # API helpers, types, theme, skeletons, wrap-up cards
│ ├── setup/ # First-time setup wizard
│ └── maintenance/ # Library maintenance panel
├── input.css # Tailwind CSS source
├── static/
│ ├── bundle.js # Built React frontend
│ ├── tailwind.css # Built Tailwind styles
│ └── logo.png # Server logo
├── lib/
│ └── data-paths.js # Data file locations + legacy migration
├── config/ # Runtime JSON data (gitignored, created on first run)
├── Dockerfile # Multi-stage production image
├── docker-compose.yml # One-command Docker deployment
├── .github/workflows/
│ └── docker-publish.yml # Publishes :latest and :beta to GHCR
├── ca_profile.xml # Unraid Community Applications maintainer profile
├── unraid/
│ └── server-manager-portal.xml # Unraid Docker template
├── .env.example # Environment variable template
├── build-version.js # Stamps version.txt and cache-bust query strings on build
├── package.json
└── .env # JWT_SECRET (not committed to git)
Runtime-generated files (stored in config/, not committed to git):
config/config.json- Server configurationconfig/users.json- User recordsconfig/audit-log.json- System action logconfig/trending-cache.json- Cached leaderboard and trending data
On first startup after an upgrade, any legacy JSON files still in the project root are automatically moved into config/.
Contributors
- Nerdy-Technician - Added Jellyfin Support 🚀
License
This project is open-source and available under the MIT License.
Install Server-Manager-Portal on Unraid in a few clicks.
Find Server-Manager-Portal in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/jl94x4/server-manager-portal:latestRuntime arguments
- Web UI
http://[IP]:[PORT:2121]/portal- Network
bridge- Shell
sh- Privileged
- false
- Extra Params
--restart=unless-stopped
Template configuration
Portal web interface port.
- Target
- 2121
- Default
- 2121
- Value
- 2121
Persistent JSON data (users, settings, caches, logs). Container path: /app/config
- Target
- /app/config
- Default
- /mnt/user/appdata/server-manager-portal/config
- Value
- /mnt/user/appdata/server-manager-portal/config
Rolling backup snapshots created from Settings. Container path: /app/backup
- Target
- /app/backup
- Default
- /mnt/user/appdata/server-manager-portal/backup
- Value
- /mnt/user/appdata/server-manager-portal/backup
Required session signing secret (minimum 32 characters). Generate a secure random string to use here.
- Target
- JWT_SECRET
Public HTTPS URL for email links (no trailing slash). For subpath hosting include the path, e.g. https://plex.example.com/portal
- Target
- PUBLIC_BASE_URL
Optional URL prefix when hosted under a subpath (e.g. /portal). Leave empty for root hosting.
- Target
- BASE_PATH
Set to true only when the portal is served over HTTPS. Must be false for plain HTTP LAN access (e.g. http://192.168.x.x:2121).
- Target
- FORCE_SECURE_COOKIES
- Default
- false|true
- Value
- false
Allow Sonarr/Radarr/Tautulli URLs on LAN/private IPs (recommended for homelab).
- Target
- ALLOW_PRIVATE_INTEGRATION_URLS
- Default
- false|true
- Value
- true
Use LAN/remote Plex URL instead of localhost when running in Docker. Auto-detected if unset; set true for Unraid.
- Target
- PLEX_PREFER_REMOTE_CONNECTION
- Default
- false|true
- Value
- true
Optional one-time token for remote first-time setup.
- Target
- SETUP_TOKEN
Optional fixed Plex OAuth client id (auto-generated on first run if empty).
- Target
- CLIENT_ID
Internal runtime data path (normally leave as /app/config).
- Target
- CONFIG_DIR
- Default
- /app/config
- Value
- /app/config
Network interface to bind.
- Target
- BIND_HOST
- Default
- 0.0.0.0
- Value
- 0.0.0.0
Application listen port inside the container.
- Target
- PORT
- Default
- 2121
- Value
- 2121