Overview
Build VPS Command Center — a self-hosted web dashboard for monitoring and managing a Linux VPS. It shows real-time system metrics (CPU, memory, disk, network), lists PM2 processes with start/stop/restart controls, displays deployed projects, and provides a web-based terminal. Think of it as a lightweight, self-hosted Heroku dashboard for solo developers managing their own VPS.
Requirements
System Metrics (Dashboard page — /)
Display real-time system metrics refreshed every 3 seconds via polling:
CPU : Current usage percentage + per-core breakdown (bar chart)
Memory : Used / Total in GB with percentage gauge
Disk : Used / Total for each mount point with percentage bars
Network : Current RX/TX bytes per second, total transferred since boot
Uptime : System uptime formatted as "Xd Xh Xm"
OS Info : Hostname, OS version, kernel, Node.js version
API endpoint: GET /api/system returns all metrics as JSON
Use the systeminformation npm package for all system data
PM2 Process Manager (/processes)
List all PM2 processes with: name, id, status (online/stopped/errored), CPU%, memory, uptime, restarts
Color-coded status badges: green for online, red for errored, gray for stopped
Action buttons per process:
Restart — calls pm2.restart(id)
Stop — calls pm2.stop(id)
Start — calls pm2.start(id) (only shown when stopped)
Action buttons require confirmation (click → "Are you sure?" → confirm/cancel)
API endpoints:
GET /api/pm2 — list all processes
POST /api/pm2/:id/restart — restart a process (returns updated process)
POST /api/pm2/:id/stop — stop a process
POST /api/pm2/:id/start — start a process
Use the pm2 npm package programmatic API (pm2.connect(), pm2.list(), etc.)
Deployed Projects (/projects)
Scan /opt/projects/ directory for subdirectories
For each project, show:
Directory name
Whether it has a package.json (and extract name/version if so)
Git info: current branch, last commit message, last commit date (via simple-git)
Disk usage of the directory (via du -sh)
API endpoint: GET /api/projects — returns array of project objects
Authentication
Simple API key authentication via API_KEY environment variable
Middleware checks Authorization: Bearer <key> header on all /api/* routes
Login page (/login) with a single password field — stores the key in an httpOnly cookie
If no API_KEY env var is set, auth is disabled (development mode)
Unauthenticated requests to protected pages redirect to /login
General
Next.js 15 App Router with TypeScript (strict mode)
Runs on configurable PORT environment variable (default 4002)
All API routes under /api/* as Next.js Route Handlers
Client-side polling every 3 seconds for system metrics and PM2 list
Error boundaries on each page section so one failure doesn't break the whole dashboard
Design Specification
Use Tailwind CSS for all styling. Follow this design system exactly. This is a dark-themed dashboard matching the Forge portal aesthetic.
Colors
Background: #0B1120 — Main page background (dark navy)
Surface: #111827 (gray-900) — Cards, panels
Surface-alt: #1F2937 (gray-800) — Hover states, secondary panels
Border: #374151 (gray-700) — Card borders, dividers
Primary text: #F9FAFB (gray-50) — Headings, important text
Secondary text:#9CA3AF (gray-400) — Labels, descriptions, muted text
Accent: #3B82F6 (blue-500) — Primary buttons, active nav, links
Accent hover: #2563EB (blue-600) — Button hover states
Success: #22C55E (green-500) — Online status, healthy metrics
Warning: #F59E0B (amber-500) — High usage warnings (>80%)
Error: #EF4444 (red-500) — Errored processes, critical alerts
Typography
Font: 'Inter', system-ui, -apple-system, sans-serif
Load via: <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
Monospace: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace (for terminal, metrics)
Load via: <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap">
Scale:
- Page title: text-2xl font-semibold text-gray-50
- Section: text-lg font-semibold text-gray-50
- Body: text-sm font-normal text-gray-300
- Label: text-xs font-medium uppercase tracking-wide text-gray-500
- Metric value: text-3xl font-bold text-gray-50 font-mono
- Code/terminal: text-sm font-mono text-gray-300
Layout — Dashboard Pattern with Sidebar
┌─────────────────────────────────────────────────────┐
│ ⬡ VPS Command Center hostname [Logout] │ ← Top bar (bg-gray-900 border-b border-gray-700)
├────────┬────────────────────────────────────────────┤
│ │ Page Title │
│ Sidebar│ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────┐ │ ← Stat cards (4-col grid)
│ □ Dash │ │ CPU │ │ Memory │ │ Disk │ │ Net│ │
│ □ PM2 │ │ 23% │ │ 4.2 GB │ │ 67% │ │ ↑↓ │ │
│ □ Proj │ └────────┘ └────────┘ └────────┘ └────┘ │
│ │ │
│ │ ┌─────────────────────────────────────┐ │ ← Detail panels
│ │ │ Detailed metrics / Process table │ │
│ │ │ │ │
│ │ └─────────────────────────────────────┘ │
├────────┴────────────────────────────────────────────┤
Component Styles
Stat card: bg-gray-900 border border-gray-700 rounded-xl p-5
Label: text-xs font-medium uppercase tracking-wide text-gray-500
Value: text-3xl font-bold text-gray-50 font-mono mt-1
Subtext: text-xs text-gray-500 mt-2
Sidebar: w-56 bg-gray-900 border-r border-gray-700 p-4 space-y-1
Active link: bg-blue-500/10 text-blue-400 font-medium rounded-lg px-3 py-2
Inactive link: text-gray-400 hover:bg-gray-800 hover:text-gray-200 rounded-lg px-3 py-2
Table: bg-gray-900 border border-gray-700 rounded-xl overflow-hidden
Header: bg-gray-800 text-xs font-medium uppercase tracking-wide text-gray-500 px-4 py-3
Row: border-b border-gray-800 hover:bg-gray-800/50 px-4 py-3 text-sm text-gray-300
Buttons:
Primary: bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium px-3 py-1.5 rounded-lg
Destructive: bg-red-500/10 hover:bg-red-500/20 text-red-400 text-sm font-medium px-3 py-1.5 rounded-lg
Ghost: hover:bg-gray-800 text-gray-400 text-sm px-3 py-1.5 rounded-lg
Status badges:
Online: bg-green-500/10 text-green-400 text-xs font-medium px-2 py-0.5 rounded-full
Stopped: bg-gray-500/10 text-gray-400 text-xs font-medium px-2 py-0.5 rounded-full
Errored: bg-red-500/10 text-red-400 text-xs font-medium px-2 py-0.5 rounded-full
Progress bars:
Track: bg-gray-800 rounded-full h-2
Fill (normal): bg-blue-500 rounded-full h-2
Fill (warning >80%): bg-amber-500 rounded-full h-2
Fill (critical >90%): bg-red-500 rounded-full h-2
Input:
bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-sm text-gray-200
focus: ring-2 ring-blue-500 border-transparent outline-none
placeholder: text-gray-600
Responsive Behavior
Mobile (< 768px): Sidebar collapses to bottom tab bar, single column layout, stat cards stack vertically
Tablet (768-1024px): Sidebar stays but narrower (w-16 with icons only), 2-column stat grid
Desktop (> 1024px): Full layout as shown above
File Structure
vps-command-center/
├── src/
│ ├── app/
│ │ ├── layout.tsx # Root layout with Inter font, dark bg, sidebar
│ │ ├── page.tsx # Dashboard page — system metrics
│ │ ├── processes/
│ │ │ └── page.tsx # PM2 process manager page
│ │ ├── projects/
│ │ │ └── page.tsx # Deployed projects page
│ │ ├── login/
│ │ │ └── page.tsx # Login page (API key auth)
│ │ ├── api/
│ │ │ ├── system/
│ │ │ │ └── route.ts # GET system metrics
│ │ │ ├── pm2/
│ │ │ │ ├── route.ts # GET list PM2 processes
│ │ │ │ └── [id]/
│ │ │ │ ├── restart/
│ │ │ │ │ └── route.ts # POST restart process
│ │ │ │ ├── stop/
│ │ │ │ │ └── route.ts # POST stop process
│ │ │ │ └── start/
│ │ │ │ └── route.ts # POST start process
│ │ │ ├── projects/
│ │ │ │ └── route.ts # GET deployed projects
│ │ │ └── auth/
│ │ │ └── route.ts # POST login (set cookie)
│ │ └── globals.css # Tailwind directives only
│ ├── components/
│ │ ├── sidebar.tsx # Navigation sidebar
│ │ ├── stat-card.tsx # Reusable metric stat card
│ │ ├── progress-bar.tsx # Color-coded progress bar
│ │ ├── status-badge.tsx # Online/stopped/errored badge
│ │ ├── process-table.tsx # PM2 process list table
│ │ ├── project-card.tsx # Deployed project card
│ │ ├── confirm-dialog.tsx # "Are you sure?" confirmation modal
│ │ └── metric-gauge.tsx # Circular or bar gauge for CPU/memory
│ ├── hooks/
│ │ ├── use-poll.ts # Generic polling hook (interval + SWR-like caching)
│ │ └── use-auth.ts # Auth state hook (check cookie, redirect to login)
│ ├── lib/
│ │ ├── system-info.ts # Server-side: systeminformation wrapper functions
│ │ ├── pm2-client.ts # Server-side: PM2 programmatic API wrapper
│ │ ├── project-scanner.ts # Server-side: scan /opt/projects/ directory
│ │ ├── auth.ts # Server-side: API key validation, cookie helpers
│ │ └── api-client.ts # Client-side: typed fetch wrapper for all API routes
│ └── types.ts # Shared TypeScript interfaces
├── public/
│ └── favicon.ico
├── tailwind.config.ts # Dark theme colors, font config
├── next.config.ts # Next.js configuration
├── tsconfig.json # Strict mode TypeScript
├── package.json
└── README.md # Setup instructions, environment variables
Dependencies
next@15 — App Router framework
react@19 — UI library
react-dom@19 — React DOM renderer
typescript@5 — Type safety (strict mode)
tailwindcss@3 — Utility CSS framework
postcss@8 — PostCSS for Tailwind
autoprefixer@10 — Vendor prefixes
systeminformation@5 — Cross-platform system metrics (CPU, memory, disk, network, OS)
pm2@5 — PM2 programmatic API for process management
simple-git@3 — Git operations (branch, last commit) for project scanning
lucide-react@0.460 — Icon set (Server, Cpu, HardDrive, Activity, etc.)
clsx@2 — Conditional class composition
Acceptance Criteria
npm install && npm run build && npm start runs without errors on port 4002
Dashboard page shows CPU, memory, disk, and network metrics that update every 3 seconds
Memory metric shows used/total in GB with percentage
Disk usage shows each mount point with percentage bar (color changes at 80%/90%)
PM2 page lists all running processes with name, status, CPU%, memory, uptime
Clicking Restart on a PM2 process restarts it (with confirmation dialog)
Clicking Stop on a PM2 process stops it (with confirmation dialog)
Status badges are color-coded: green=online, red=errored, gray=stopped
Projects page lists directories under /opt/projects/ with git info
Each project card shows: name, git branch, last commit message, last commit date
Login page accepts the API key and sets an httpOnly cookie
Protected pages redirect to /login when no valid cookie exists
When API_KEY env var is not set, auth is disabled
Sidebar navigation highlights the active page
UI follows the dark design specification (dark navy background, gray-900 cards)
Responsive: usable on mobile (375px), tablet (768px), desktop (1280px)
All interactive elements have hover/focus states
TypeScript compiles with strict: true and no errors
Edge Cases
PM2 not installed or not running → show "PM2 not available" message, don't crash
No projects in /opt/projects/ → show empty state with "No projects deployed yet"
/opt/projects/ directory doesn't exist → handle gracefully, show empty state
Git not initialized in a project directory → show "No git info" for that project
System metrics API fails → show "Unable to fetch metrics" with retry button
PM2 action fails (e.g., process doesn't exist) → show error toast, don't crash
Very long process names → truncate with ellipsis in table
API key cookie expired or invalid → redirect to login, clear stale cookie
Multiple mount points → show all, not just root
Network interface with no traffic → show 0 B/s, not error
Hard Constraints
No external database — all data comes from live system queries and PM2 API
No Docker — runs directly on the host OS with Node.js
No custom CSS files — all styling via Tailwind utility classes in globals.css directives only
Port 4002 — must not conflict with existing services on 3000 (Dark Factory) and 4001 (Forge)
Design — must follow the Design Specification section exactly (dark theme)
Responsive — must be functional on mobile (375px width minimum)
Overview
Build VPS Command Center — a self-hosted web dashboard for monitoring and managing a Linux VPS. It shows real-time system metrics (CPU, memory, disk, network), lists PM2 processes with start/stop/restart controls, displays deployed projects, and provides a web-based terminal. Think of it as a lightweight, self-hosted Heroku dashboard for solo developers managing their own VPS.
Requirements
System Metrics (Dashboard page —
/)GET /api/systemreturns all metrics as JSONsysteminformationnpm package for all system dataPM2 Process Manager (
/processes)pm2.restart(id)pm2.stop(id)pm2.start(id)(only shown when stopped)GET /api/pm2— list all processesPOST /api/pm2/:id/restart— restart a process (returns updated process)POST /api/pm2/:id/stop— stop a processPOST /api/pm2/:id/start— start a processpm2npm package programmatic API (pm2.connect(),pm2.list(), etc.)Deployed Projects (
/projects)/opt/projects/directory for subdirectoriespackage.json(and extract name/version if so)simple-git)du -sh)GET /api/projects— returns array of project objectsAuthentication
API_KEYenvironment variableAuthorization: Bearer <key>header on all/api/*routes/login) with a single password field — stores the key in an httpOnly cookieAPI_KEYenv var is set, auth is disabled (development mode)/loginGeneral
PORTenvironment variable (default 4002)/api/*as Next.js Route HandlersDesign Specification
Use Tailwind CSS for all styling. Follow this design system exactly. This is a dark-themed dashboard matching the Forge portal aesthetic.
Colors
Typography
Layout — Dashboard Pattern with Sidebar
Component Styles
Responsive Behavior
File Structure
Dependencies
Acceptance Criteria
npm install && npm run build && npm startruns without errors on port 4002Edge Cases
Hard Constraints