-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
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/systemreturns all metrics as JSON - Use the
systeminformationnpm 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)
- Restart — calls
- Action buttons require confirmation (click → "Are you sure?" → confirm/cancel)
- API endpoints:
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 process
- Use the
pm2npm 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_KEYenvironment 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_KEYenv 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
PORTenvironment 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 startruns 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)
Reactions are currently unavailable