mkdir -p backend/app/{collectors,services,api,models}
cd backend
python -m venv venv
pip install fastapi uvicorn websockets asyncssh docker httpx pydantic pyyamlnpm create vue@latest frontend -- --template vue-ts
cd frontend
npm install -D tailwindcss postcss autoprefixer
npm install pinia vue-router
npx tailwindcss init -pServerMonitor/
├── backend/
│ ├── app/
│ │ ├── __init__.py
│ │ ├── main.py
│ │ ├── config.py
│ │ ├── models/
│ │ ├── collectors/
│ │ ├── services/
│ │ └── api/
│ ├── config.yaml
│ └── requirements.txt
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── composables/
│ │ ├── stores/
│ │ ├── types/
│ │ └── views/
│ └── package.json
├── docker-compose.yml
├── requirements.md
├── task_plan.md
└── notes.md
- Load
config.yamlwith system definitions - Pydantic models for config validation
- Support for credentials and connection details
class SystemStatus(str, Enum):
HEALTHY = "healthy"
WARNING = "warning"
CRITICAL = "critical"
OFFLINE = "offline"
class SystemMetrics(BaseModel):
id: str
name: str
type: str # linux, docker, qbittorrent, unifi, unas
status: SystemStatus
last_updated: datetime
metrics: dict[str, Any]- CORS middleware for local dev
- Include REST and WebSocket routers
- Startup event to initialize collectors
class BaseCollector(ABC):
@abstractmethod
async def collect(self) -> SystemMetrics:
pass
@abstractmethod
async def check_health(self) -> SystemStatus:
pass- SSH connection using
asyncssh - Parse
/proc/statfor CPU - Parse
/proc/meminfofor RAM - Run
df -hfor disk usage - Run
sensorsfor temperatures - Calculate uptime from
/proc/uptime
- Connect to Docker socket/API
- List containers with status
- Get resource usage per container
- Aggregate host-level stats
- Authenticate to Web API
- Fetch
/api/v2/sync/maindata - Extract: active torrents, DL/UL speeds, queue
- Connect to UniFi Controller API
- Fetch device list and status
- Get client count and bandwidth
- SSH or API based on NAS type
- Fetch pool/volume status
- Get disk health and capacity
- Initialize all configured collectors
- Run collection loop (every 5-10 sec)
- Broadcast updates to WebSocket clients
- Accept connections at
/ws - Send initial state on connect
- Push updates on each collection cycle
- Handle client disconnects gracefully
GET /api/systems- List all systemsGET /api/systems/{id}- Get single system detailsGET /api/config- Get current config (sanitized)
- Dark mode via
classstrategy - Custom colors matching design spec
- Typography and spacing setup
interface MetricsState {
systems: Map<string, SystemMetrics>
connected: boolean
lastUpdate: Date | null
}- Connect to backend WebSocket
- Auto-reconnect on disconnect
- Update Pinia store on messages
- Toggle dark/light mode
- Persist preference in localStorage
- Apply class to document root
- Responsive grid layout (1-4 columns)
- Map over systems from store
- Render SystemCard for each
- Show: name, type icon, status indicator
- Key metrics preview (CPU, RAM)
- Click to navigate to detail view
- Color-coded dot/badge
- Pulse animation for warnings/critical
routes: [
{ path: '/', component: Dashboard },
{ path: '/system/:id', component: SystemDetail }
]- CPU usage (per-core bars)
- Memory breakdown (used/cached/free)
- Disk usage per mount
- Temperature readings
- Uptime display
- Container list with status
- Per-container CPU/memory
- Image and port info
- Active torrent list
- Global speeds
- Queue statistics
- Device status list
- Client count
- Bandwidth graphs
- Pool/volume status
- Disk health indicators
- Capacity bars
- Sun/Moon icon button
- Smooth transition animation
- Sync with composable
- Define colors as CSS vars
- Switch values based on
.darkclass
- Offline system display (greyed out card)
- Connection lost indicator
- Retry logic for collectors
- Skeleton loaders on initial load
- Spinner during reconnection
- Mobile breakpoints
- Tablet layout
- Large screen optimization
services:
backend:
build: ./backend
ports:
- "8000:8000"
volumes:
- ./config.yaml:/app/config.yaml
frontend:
build: ./frontend
ports:
- "3000:80"
depends_on:
- backendpoll_interval: 5
systems:
- id: linux-server-1
type: linux
name: "Main Server"
host: 192.168.1.100
ssh_user: monitor
ssh_key_path: /path/to/key
- id: docker-host
type: docker
name: "Docker Host"
socket: unix:///var/run/docker.sock
- id: qbit
type: qbittorrent
name: "qBittorrent"
url: http://192.168.1.100:8080
username: admin
password: secret| Milestone | Description | Deliverable |
|---|---|---|
| M1 | Backend runs, single Linux collector works | /api/systems returns data |
| M2 | WebSocket streams metrics to browser | Console logs show updates |
| M3 | Dashboard shows system cards | Visual grid renders |
| M4 | All collectors implemented | 5 system types working |
| M5 | Drill-down views complete | Click-through works |
| M6 | Theme toggle works | Dark/Light switch |
| M7 | Docker Compose runs full stack | One-command deployment |
Ready to begin Phase 2: Project Scaffolding
Run the following to start:
# Create backend structure
mkdir -p backend/app/{collectors,services,api,models}
touch backend/app/__init__.py
touch backend/requirements.txt
# Create frontend with Vue
npm create vue@latest frontend