Skip to content

Build live Markdown preview app #3

@ibuzzardo

Description

@ibuzzardo

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)

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions