1194 lines
42 KiB
Markdown
1194 lines
42 KiB
Markdown
# Homelab Topology - Remaining Features Implementation
|
|
|
|
## TL;DR
|
|
|
|
> **Quick Summary**: Implement 11 remaining features for the homelab topology visualizer, including backend API for SSH discovery, real data tabs (Config/Files/Usage), filters, persistence, and UX enhancements.
|
|
>
|
|
> **Deliverables**:
|
|
> - Express backend API server with SSH discovery endpoints
|
|
> - Config Tab showing real docker-compose.yml content
|
|
> - Files Tab showing real mounted volumes
|
|
> - Usage Tab showing live docker stats
|
|
> - Type and Status filters in Header
|
|
> - Configurable poll interval UI
|
|
> - LocalStorage persistence for settings
|
|
> - Command palette (Cmd+K)
|
|
> - Stale data warning banner
|
|
> - Path highlighting for selected nodes
|
|
>
|
|
> **Estimated Effort**: Large
|
|
> **Parallel Execution**: YES - 3 waves
|
|
> **Critical Path**: Backend API → SSH Discovery → Real Data Tabs → Filters → UX Enhancements
|
|
|
|
---
|
|
|
|
## Context
|
|
|
|
### Original Request
|
|
Implement all remaining tasks for the homelab topology visualizer:
|
|
- High Priority: SSH Discovery, Config Tab, Files Tab, Usage Tab
|
|
- Medium Priority: Type Filter, Status Filter, Configurable Poll, LocalStorage
|
|
- Lower Priority: Command Palette, Stale Warning, Path Highlighting
|
|
|
|
### Interview Summary
|
|
**Key Discussions**:
|
|
- SSH Discovery: Requires backend API (browser can't SSH directly)
|
|
- Config/Files/Usage Tabs: Need real data via SSH + docker commands
|
|
- Filters: Toggle buttons in Header for type/status filtering
|
|
- Persistence: Zustand persist middleware for localStorage
|
|
- UX: Command palette, stale warning, path highlighting
|
|
|
|
**Research Findings**:
|
|
- Current project uses React + Vite + Zustand + React Flow + dagre
|
|
- `sshDiscovery.ts` exists but not connected to frontend
|
|
- App.tsx has 30s polling with countdown timer
|
|
- RightPanel tabs show placeholder data
|
|
|
|
### Metis Review
|
|
**Identified Gaps** (addressed):
|
|
- SSH Authentication: Using environment variables/config file, NOT localStorage
|
|
- Error Handling: Graceful degradation for partial failures
|
|
- Security: All SSH operations server-side only
|
|
- Performance: Discovery timeout of 30s per host
|
|
|
|
---
|
|
|
|
## Work Objectives
|
|
|
|
### Core Objective
|
|
Complete the homelab topology visualizer with real infrastructure data via SSH discovery and enhanced UX features.
|
|
|
|
### Concrete Deliverables
|
|
1. `server/` - Express backend with SSH discovery API
|
|
2. `server/routes/discover.ts` - SSH discovery endpoints
|
|
3. `server/routes/config.ts` - Config file retrieval
|
|
4. `server/routes/files.ts` - Volume discovery
|
|
5. `server/routes/stats.ts` - Docker stats endpoint
|
|
6. `src/components/Header.tsx` - Updated with filters and poll interval
|
|
7. `src/components/RightPanel.tsx` - Config/Files/Usage tabs with real data
|
|
8. `src/components/CommandPalette.tsx` - New Cmd+K modal
|
|
9. `src/components/StaleWarning.tsx` - New warning banner
|
|
10. `src/store/topologyStore.ts` - Updated with persist middleware
|
|
|
|
### Definition of Done
|
|
- [ ] All 11 features implemented and functional
|
|
- [ ] Backend API running on port 3001
|
|
- [ ] Frontend connects to backend for discovery
|
|
- [ ] All filters work and persist across refresh
|
|
- [ ] Command palette functional with Cmd+K
|
|
- [ ] Stale warning appears after 3 failed discoveries
|
|
- [ ] Path highlighting shows ancestor nodes
|
|
|
|
### Must Have
|
|
- Backend API for SSH operations (security: server-side only)
|
|
- Real data in Config/Files/Usage tabs
|
|
- Type and Status filters functional
|
|
- Settings persist in localStorage
|
|
|
|
### Must NOT Have (Guardrails)
|
|
- **SECURITY**: NEVER store SSH credentials in localStorage or frontend
|
|
- **SECURITY**: NEVER expose SSH credentials in API responses
|
|
- **SCOPE**: No user authentication system (single-user local tool)
|
|
- **SCOPE**: No WebSocket (polling is sufficient)
|
|
- **SCOPE**: No historical metrics storage (live data only)
|
|
- **SCOPE**: No alerting/notification system
|
|
- **IMPLEMENTATION**: No database (file-based config is sufficient)
|
|
- **AI-SLOP**: No over-engineering - simple Express server, minimal routes
|
|
|
|
---
|
|
|
|
## Verification Strategy
|
|
|
|
### Test Decision
|
|
- **Infrastructure exists**: YES (frontend has test config)
|
|
- **Automated tests**: NO (tests-after approach)
|
|
- **Framework**: bun test
|
|
- **Agent-Executed QA**: ALWAYS (mandatory for all tasks)
|
|
|
|
### QA Policy
|
|
Every task includes agent-executed QA scenarios:
|
|
- **Backend API**: Use Bash (curl) — Send requests, assert status + response fields
|
|
- **Frontend UI**: Use Playwright — Navigate, interact, assert DOM, screenshot
|
|
- **Integration**: Use Playwright + curl combined
|
|
|
|
Evidence saved to `.sisyphus/evidence/task-{N}-{scenario-slug}.{ext}`
|
|
|
|
---
|
|
|
|
## Execution Strategy
|
|
|
|
### Parallel Execution Waves
|
|
|
|
```
|
|
Wave 1 (Start Immediately — 5 parallel tasks):
|
|
├── Task 1: Express Backend Foundation [quick]
|
|
├── Task 2: Backend Types & Config Schema [quick]
|
|
├── Task 3: Type Filter UI [quick]
|
|
├── Task 4: Status Filter UI [quick]
|
|
└── Task 5: LocalStorage Persistence [quick]
|
|
|
|
Wave 2 (After Wave 1 — SSH discovery, then parallel data tabs):
|
|
├── Task 6: SSH Discovery API Endpoint [deep] (blocks 7-10)
|
|
├── Task 7: Wire Frontend to Discovery API [quick]
|
|
├── Task 8: Config Tab with Real Data [quick]
|
|
├── Task 9: Files Tab with Real Data [quick]
|
|
└── Task 10: Usage Tab with Real Data [quick]
|
|
|
|
Wave 3 (After Wave 2 — 4 parallel UX enhancements):
|
|
├── Task 11: Configurable Poll Interval [quick]
|
|
├── Task 12: Command Palette [visual-engineering]
|
|
├── Task 13: Stale Data Warning [quick]
|
|
└── Task 14: Path Highlighting [visual-engineering]
|
|
|
|
Wave FINAL (After ALL tasks — 4 parallel reviews):
|
|
├── Task F1: Plan Compliance Audit (oracle)
|
|
├── Task F2: Code Quality Review (unspecified-high)
|
|
├── Task F3: Real Manual QA (unspecified-high)
|
|
└── Task F4: Scope Fidelity Check (deep)
|
|
|
|
Critical Path: Task 1 → Task 6 → Task 7 → Task 8/9/10 → Task 11/12/13/14 → F1-F4
|
|
Parallel Speedup: ~60% faster than sequential
|
|
Max Concurrent: 5 (Wave 1)
|
|
```
|
|
|
|
### Dependency Matrix
|
|
|
|
| Task | Depends On | Blocks |
|
|
|------|------------|--------|
|
|
| 1 | — | 6, 8, 9, 10 |
|
|
| 2 | — | 6, 8, 9, 10 |
|
|
| 3 | — | 12 |
|
|
| 4 | — | 12 |
|
|
| 5 | — | — |
|
|
| 6 | 1, 2 | 7, 8, 9, 10 |
|
|
| 7 | 6 | 11, 13, 14 |
|
|
| 8 | 6, 7 | — |
|
|
| 9 | 6, 7 | — |
|
|
| 10 | 6, 7 | — |
|
|
| 11 | 7 | — |
|
|
| 12 | 3, 4 | — |
|
|
| 13 | 7 | — |
|
|
| 14 | 7 | — |
|
|
|
|
### Agent Dispatch Summary
|
|
|
|
- **Wave 1**: 5 tasks — T1 → `quick` + `system-admin`, T2 → `quick`, T3-T4 → `quick` + `frontend-ui-ux`, T5 → `quick`
|
|
- **Wave 2**: 5 tasks — T6 → `deep` + `system-admin` + `ssh`, T7-T10 → `quick`
|
|
- **Wave 3**: 4 tasks — T11/T13 → `quick` + `frontend-ui-ux`, T12/T14 → `visual-engineering`
|
|
- **FINAL**: 4 tasks — F1 → `oracle`, F2-F3 → `unspecified-high`, F4 → `deep`
|
|
|
|
---
|
|
|
|
## TODOs
|
|
|
|
- [ ] 1. **Create Express Backend Foundation**
|
|
|
|
**What to do**:
|
|
- Create `server/` directory at project root
|
|
- Create `server/index.ts` with Express app listening on port 3001
|
|
- Add CORS middleware for frontend communication
|
|
- Create `/api/health` endpoint returning `{"status":"ok"}`
|
|
- Add npm script `"server": "tsx server/index.ts"` to package.json
|
|
- Install dependencies: `express`, `cors`, `@types/express`, `@types/cors`
|
|
|
|
**Must NOT do**:
|
|
- Do NOT add authentication middleware (out of scope)
|
|
- Do NOT add WebSocket support (polling is sufficient)
|
|
- Do NOT over-engineer with middleware layers
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Standard Express server setup, well-documented pattern
|
|
- **Skills**: [`system-admin`]
|
|
- `system-admin`: Server setup and port configuration
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 1 (with Tasks 2, 3, 4, 5)
|
|
- **Blocks**: Tasks 6, 8, 9, 10
|
|
- **Blocked By**: None
|
|
|
|
**References**:
|
|
- `package.json:13-22` - Existing dependencies, add express/cors
|
|
- `src/services/sshDiscovery.ts:1-44` - SSH client setup pattern to reference
|
|
- Existing Vite config shows project structure
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `server/index.ts` exists with Express app
|
|
- [ ] `npm run server` starts server on port 3001
|
|
- [ ] `curl http://localhost:3001/api/health` returns `{"status":"ok"}`
|
|
- [ ] CORS enabled for `http://localhost:3000`
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Backend health check works
|
|
Tool: Bash (curl)
|
|
Steps:
|
|
1. cd /home/christopher/homelab-topology
|
|
2. bun run server &
|
|
3. sleep 2
|
|
4. curl -s http://localhost:3001/api/health
|
|
Expected Result: {"status":"ok"}
|
|
Evidence: .sisyphus/evidence/task-1-health-check.json
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(backend): add Express server foundation`
|
|
- Files: `server/index.ts`, `package.json`
|
|
|
|
---
|
|
|
|
- [ ] 2. **Define Backend Types and Config Schema**
|
|
|
|
**What to do**:
|
|
- Create `server/types.ts` with TypeScript interfaces:
|
|
- `HostConfig`: name, ip, sshUser, sshKeyPath (optional)
|
|
- `DiscoveryResponse`: hosts, timestamp, errors
|
|
- `ConfigResponse`: yaml, path, error
|
|
- `FilesResponse`: volumes array (source, destination, mode)
|
|
- `StatsResponse`: cpu, memory, network (rx/tx)
|
|
- Create `server/config.ts` with host configuration loader:
|
|
- Load from environment variables first (SSH_HOSTS, SSH_USER, SSH_KEY)
|
|
- Fallback to `server/config.json` file
|
|
- Export `getHostConfigs(): HostConfig[]`
|
|
- Create sample `server/config.example.json` with structure
|
|
|
|
**Must NOT do**:
|
|
- Do NOT store credentials in code
|
|
- Do NOT create encrypted vault (out of scope)
|
|
- Do NOT add credential rotation logic
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Type definitions and simple config loading
|
|
- **Skills**: []
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 1 (with Tasks 1, 3, 4, 5)
|
|
- **Blocks**: Tasks 6, 8, 9, 10
|
|
- **Blocked By**: None
|
|
|
|
**References**:
|
|
- `src/types/index.ts` - Existing type patterns to match
|
|
- `src/services/sshDiscovery.ts:3-28` - Existing SSH config interface
|
|
- `src/data/staticConfig.ts:58-101` - Existing host definitions
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `server/types.ts` exists with all interfaces
|
|
- [ ] `server/config.ts` exports `getHostConfigs()`
|
|
- [ ] `server/config.example.json` shows expected format
|
|
- [ ] Types compile without errors: `tsc --noEmit`
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Config loader returns host configs
|
|
Tool: Bash (node)
|
|
Steps:
|
|
1. cd /home/christopher/homelab-topology
|
|
2. Create test config: echo '{"hosts":[{"name":"test","ip":"127.0.0.1"}]}' > server/config.json
|
|
3. node -e "import('./server/config.ts').then(m => console.log(m.getHostConfigs()))"
|
|
Expected Result: Array with host configs
|
|
Evidence: .sisyphus/evidence/task-2-config-loader.txt
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(backend): add types and config schema`
|
|
- Files: `server/types.ts`, `server/config.ts`, `server/config.example.json`
|
|
|
|
---
|
|
|
|
- [ ] 3. **Implement Type Filter UI**
|
|
|
|
**What to do**:
|
|
- Add `typeFilters: NodeType[]` to topologyStore (default: all types)
|
|
- Add `toggleTypeFilter(type: NodeType)` action to store
|
|
- Update `getFilteredNodes()` to filter by typeFilters
|
|
- Add filter toggle buttons in Header.tsx after orientation dropdown
|
|
- Each button shows node type icon and is toggleable (active/inactive state)
|
|
- Use existing node type colors for button styling
|
|
|
|
**Must NOT do**:
|
|
- Do NOT add "select all/none" buttons (keep it simple)
|
|
- Do NOT create a dropdown menu (use inline toggles)
|
|
- Do NOT add filter descriptions/tooltips (icons sufficient)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Simple toggle buttons with store integration
|
|
- **Skills**: [`frontend-ui-ux`]
|
|
- `frontend-ui-ux`: Filter button design and styling
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 1 (with Tasks 1, 2, 4, 5)
|
|
- **Blocks**: Task 12
|
|
- **Blocked By**: None
|
|
|
|
**References**:
|
|
- `src/store/topologyStore.ts:79-125` - Existing getFilteredNodes() to extend
|
|
- `src/types/index.ts:1-11` - NodeType definitions
|
|
- `src/utils/colors.ts` - Node type colors for styling
|
|
- `src/components/Header.tsx:58-73` - Existing button styling pattern
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `typeFilters` added to store with all types as default
|
|
- [ ] Toggle buttons visible in Header
|
|
- [ ] Clicking filter hides corresponding nodes
|
|
- [ ] Multiple filters can be active simultaneously
|
|
- [ ] Filter state persists (will be tested in Task 5)
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Type filter hides gateway nodes
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click type filter button for "gateway"
|
|
3. Wait for graph to update
|
|
4. Check if gateway node is visible
|
|
Expected Result: Gateway node not in DOM
|
|
Evidence: .sisyphus/evidence/task-3-type-filter.png
|
|
|
|
Scenario: Multiple type filters work together
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click filter for "vlan" and "wifi"
|
|
3. Count visible nodes
|
|
Expected Result: Only nodes NOT of type vlan/wifi visible
|
|
Evidence: .sisyphus/evidence/task-3-multi-filter.png
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ui): add type filter toggles`
|
|
- Files: `src/store/topologyStore.ts`, `src/components/Header.tsx`
|
|
|
|
---
|
|
|
|
- [ ] 4. **Implement Status Filter UI**
|
|
|
|
**What to do**:
|
|
- Add `statusFilter: 'all' | 'running' | 'stopped'` to topologyStore (default: 'all')
|
|
- Add `setStatusFilter(status: string)` action to store
|
|
- Update `getFilteredNodes()` to filter by status
|
|
- Add dropdown in Header.tsx after type filters with options: All, Running, Stopped
|
|
- Use existing status colors for visual indication
|
|
|
|
**Must NOT do**:
|
|
- Do NOT add "unknown" status filter (not useful)
|
|
- Do NOT create multi-select (single status at a time)
|
|
- Do NOT add status counts (complexity)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Simple dropdown with store integration
|
|
- **Skills**: [`frontend-ui-ux`]
|
|
- `frontend-ui-ux`: Dropdown styling
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 1 (with Tasks 1, 2, 3, 5)
|
|
- **Blocks**: Task 12
|
|
- **Blocked By**: None
|
|
|
|
**References**:
|
|
- `src/store/topologyStore.ts:79-125` - Existing getFilteredNodes() to extend
|
|
- `src/types/index.ts:15` - Status type definition
|
|
- `src/utils/colors.ts` - getStatusColor function
|
|
- `src/components/Header.tsx:78-89` - Existing dropdown pattern (orientation)
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `statusFilter` added to store with 'all' as default
|
|
- [ ] Dropdown visible in Header with 3 options
|
|
- [ ] Selecting "Running" shows only running nodes
|
|
- [ ] Selecting "Stopped" shows only stopped nodes
|
|
- [ ] Filter state persists (will be tested in Task 5)
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Status filter shows only running nodes
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click status dropdown, select "Running"
|
|
3. Check all visible nodes have status "running"
|
|
Expected Result: All visible nodes show running status
|
|
Evidence: .sisyphus/evidence/task-4-running-filter.png
|
|
|
|
Scenario: Status filter shows only stopped nodes
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click status dropdown, select "Stopped"
|
|
3. Verify no running nodes visible
|
|
Expected Result: Only stopped/unknown nodes visible
|
|
Evidence: .sisyphus/evidence/task-4-stopped-filter.png
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ui): add status filter dropdown`
|
|
- Files: `src/store/topologyStore.ts`, `src/components/Header.tsx`
|
|
|
|
---
|
|
|
|
- [ ] 5. **Implement LocalStorage Persistence**
|
|
|
|
**What to do**:
|
|
- Add `zustand/middleware` persist to topologyStore
|
|
- Persist these keys: viewMode, orientation, typeFilters, statusFilter, leftPanelOpen, rightPanelOpen, pollInterval
|
|
- Set storage key as `homelab-topology-settings`
|
|
- Exclude from persistence: nodes, edges, selectedNodeId, isLoading, networkInfo, hosts
|
|
- Add version number for future migrations
|
|
|
|
**Must NOT do**:
|
|
- Do NOT persist SSH credentials (SECURITY)
|
|
- Do NOT persist topology data (nodes/edges)
|
|
- Do NOT add export/import functionality (out of scope)
|
|
- Do NOT add profile switching (out of scope)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Zustand persist middleware is straightforward
|
|
- **Skills**: []
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 1 (with Tasks 1, 2, 3, 4)
|
|
- **Blocks**: None
|
|
- **Blocked By**: None
|
|
|
|
**References**:
|
|
- `src/store/topologyStore.ts:1-127` - Existing store structure
|
|
- Zustand persist docs: https://zustand.docs.pmnd.rs/middleware/persist
|
|
- `package.json:21` - Zustand already installed
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] Persist middleware added to store
|
|
- [ ] Settings saved to localStorage on change
|
|
- [ ] Settings restored on page load
|
|
- [ ] Sensitive data NOT persisted
|
|
- [ ] Page refresh preserves filters and view mode
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Settings persist across page refresh
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Set view mode to "Network"
|
|
3. Set orientation to "Top to Bottom"
|
|
4. Toggle left panel closed
|
|
5. Refresh page (F5)
|
|
6. Check view mode, orientation, panel state
|
|
Expected Result: All settings match pre-refresh state
|
|
Evidence: .sisyphus/evidence/task-5-persistence.png
|
|
|
|
Scenario: Nodes NOT persisted in localStorage
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Open browser DevTools > Application > Local Storage
|
|
3. Check homelab-topology-settings value
|
|
Expected Result: No 'nodes' or 'edges' keys in storage
|
|
Evidence: .sisyphus/evidence/task-5-no-sensitive-data.txt
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ui): add localStorage persistence`
|
|
- Files: `src/store/topologyStore.ts`
|
|
|
|
---
|
|
|
|
- [ ] 6. **Implement SSH Discovery API Endpoint**
|
|
|
|
**What to do**:
|
|
- Create `server/routes/discover.ts` with Express router
|
|
- Implement `POST /api/discover` endpoint:
|
|
- Load host configs from `getHostConfigs()`
|
|
- Call `discoverHostViaSSH()` from existing sshDiscovery.ts for each host
|
|
- Run discoveries in parallel with Promise.all
|
|
- Handle errors gracefully (partial success)
|
|
- Return DiscoveryResponse with hosts, timestamp, errors array
|
|
- Add 30-second timeout per host
|
|
- Import and use existing sshDiscovery functions
|
|
|
|
**Must NOT do**:
|
|
- Do NOT expose SSH credentials in response
|
|
- Do NOT add retry logic (keep simple)
|
|
- Do NOT cache results (always fresh discovery)
|
|
- Do NOT add WebSocket push (polling is sufficient)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `deep`
|
|
- Reason: Complex async operations, error handling, SSH integration
|
|
- **Skills**: [`system-admin`, `ssh`]
|
|
- `system-admin`: Understanding SSH operations
|
|
- `ssh`: SSH client configuration and error handling
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: NO (depends on Tasks 1, 2)
|
|
- **Parallel Group**: Wave 2 (must complete before 7-10)
|
|
- **Blocks**: Tasks 7, 8, 9, 10
|
|
- **Blocked By**: Tasks 1, 2
|
|
|
|
**References**:
|
|
- `src/services/sshDiscovery.ts:61-121` - Existing discoverHostViaSSH function
|
|
- `src/services/sshDiscovery.ts:114-132` - discoverAllHostsSSH pattern
|
|
- `server/types.ts` - HostConfig, DiscoveryResponse interfaces
|
|
- `server/config.ts` - getHostConfigs() function
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `server/routes/discover.ts` exists
|
|
- [ ] `POST /api/discover` endpoint functional
|
|
- [ ] Returns JSON with hosts array, timestamp, errors
|
|
- [ ] Handles SSH failures gracefully (partial success)
|
|
- [ ] No SSH credentials in response
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Discovery API returns host data
|
|
Tool: Bash (curl)
|
|
Steps:
|
|
1. Ensure server running: bun run server
|
|
2. curl -X POST http://localhost:3001/api/discover
|
|
3. Parse JSON response
|
|
Expected Result: Response has hosts array with host objects
|
|
Evidence: .sisyphus/evidence/task-6-discover-success.json
|
|
|
|
Scenario: Discovery handles SSH failure gracefully
|
|
Tool: Bash (curl)
|
|
Steps:
|
|
1. Add unreachable host to config
|
|
2. curl -X POST http://localhost:3001/api/discover
|
|
3. Check response has errors array
|
|
Expected Result: Response includes error for unreachable host, other hosts succeed
|
|
Evidence: .sisyphus/evidence/task-6-discover-partial.json
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(api): implement SSH discovery endpoint`
|
|
- Files: `server/routes/discover.ts`, `server/index.ts` (register router)
|
|
|
|
---
|
|
|
|
- [ ] 7. **Wire Frontend to Discovery API**
|
|
|
|
**What to do**:
|
|
- Update `App.tsx` `loadData()` to call backend API instead of simulated discovery
|
|
- Add API_BASE_URL constant (default: `http://localhost:3001`)
|
|
- Handle API errors with fallback to simulated data
|
|
- Add loading states per host (show which hosts are still loading)
|
|
- Update Footer to show "Live data" or "Simulated data" indicator
|
|
|
|
**Must NOT do**:
|
|
- Do NOT hardcode API URL (use environment variable)
|
|
- Do NOT add complex error UI (console.error + fallback is enough)
|
|
- Do NOT add retry logic in frontend (keep simple)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Simple fetch call with error handling
|
|
- **Skills**: []
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: NO (depends on Task 6)
|
|
- **Parallel Group**: Wave 2 (runs after Task 6)
|
|
- **Blocks**: Tasks 8, 9, 10, 11, 13, 14
|
|
- **Blocked By**: Task 6
|
|
|
|
**References**:
|
|
- `src/App.tsx:33-73` - Existing loadData() function to modify
|
|
- `src/services/discovery.ts:264-340` - Existing discoverHosts (will be fallback)
|
|
- `src/services/sshDiscovery.ts` - Will be called via API instead
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] Frontend calls backend API for discovery
|
|
- [ ] Falls back to simulated data on API error
|
|
- [ ] Loading state shows during API call
|
|
- [ ] Footer shows data source indicator
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Frontend shows real data from API
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Start backend: bun run server
|
|
2. Start frontend: bun run dev
|
|
3. Navigate to http://localhost:3000
|
|
4. Check footer for "Live data" indicator
|
|
Expected Result: Topology shows real SSH-discovered data
|
|
Evidence: .sisyphus/evidence/task-7-live-data.png
|
|
|
|
Scenario: Frontend falls back on API error
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Stop backend server
|
|
2. Navigate to http://localhost:3000
|
|
3. Check footer for "Simulated data" indicator
|
|
Expected Result: Topology shows simulated data, no crash
|
|
Evidence: .sisyphus/evidence/task-7-fallback.png
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ui): wire frontend to discovery API`
|
|
- Files: `src/App.tsx`
|
|
|
|
---
|
|
|
|
- [ ] 8. **Implement Config Tab with Real Data**
|
|
|
|
**What to do**:
|
|
- Create `server/routes/config.ts` with Express router
|
|
- Implement `GET /api/config/:host/:container` endpoint:
|
|
- SSH to host
|
|
- Find docker-compose.yml location (check common paths)
|
|
- Read file content
|
|
- Parse YAML to find specific service
|
|
- Return ConfigResponse with yaml string, path, error if any
|
|
- Update `RightPanel.tsx` ConfigTab:
|
|
- Fetch config from API when service node selected
|
|
- Display YAML with syntax highlighting (use `<pre>` with monospace)
|
|
- Show error message if config not found
|
|
- Show "Loading..." state during fetch
|
|
|
|
**Must NOT do**:
|
|
- Do NOT add YAML editing (read-only)
|
|
- Do NOT show full docker-compose (only selected service section)
|
|
- Do NOT add config validation (just display)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Simple file read and display
|
|
- **Skills**: []
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 2 (with Tasks 9, 10, after Task 7)
|
|
- **Blocks**: None
|
|
- **Blocked By**: Tasks 6, 7
|
|
|
|
**References**:
|
|
- `src/components/RightPanel.tsx:122-141` - Existing ConfigTab to update
|
|
- `src/services/sshDiscovery.ts:46-59` - SSH exec pattern
|
|
- Common docker-compose paths: `./docker-compose.yml`, `/opt/docker-compose.yml`
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `GET /api/config/:host/:container` endpoint exists
|
|
- [ ] ConfigTab fetches and displays YAML
|
|
- [ ] Shows error message when config not found
|
|
- [ ] Shows "Loading..." during fetch
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Config tab shows docker-compose content
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click on a service node (e.g., traefik)
|
|
3. Click "Config" tab in right panel
|
|
Expected Result: YAML content displayed in monospace font
|
|
Evidence: .sisyphus/evidence/task-8-config-display.png
|
|
|
|
Scenario: Config tab handles missing file
|
|
Tool: Bash (curl)
|
|
Steps:
|
|
1. curl http://localhost:3001/api/config/nonexistent/container
|
|
Expected Result: Response with error field, not 500
|
|
Evidence: .sisyphus/evidence/task-8-config-error.json
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ui): add real config data to Config tab`
|
|
- Files: `server/routes/config.ts`, `src/components/RightPanel.tsx`, `server/index.ts`
|
|
|
|
---
|
|
|
|
- [ ] 9. **Implement Files Tab with Real Data**
|
|
|
|
**What to do**:
|
|
- Create `server/routes/files.ts` with Express router
|
|
- Implement `GET /api/files/:host/:container` endpoint:
|
|
- SSH to host
|
|
- Run `docker inspect <container> --format '{{json .Mounts}}'`
|
|
- Parse JSON array of mounts
|
|
- Return FilesResponse with volumes array (source, destination, mode)
|
|
- Update `RightPanel.tsx` FilesTab:
|
|
- Fetch files from API when service node selected
|
|
- Display list of volume mounts with Source → Destination
|
|
- Show "No volumes" message if empty
|
|
- Show "Loading..." state during fetch
|
|
|
|
**Must NOT do**:
|
|
- Do NOT browse files (just show mount points)
|
|
- Do NOT add file content preview
|
|
- Do NOT follow symlinks (show as-is)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Simple docker inspect parsing
|
|
- **Skills**: []
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 2 (with Tasks 8, 10, after Task 7)
|
|
- **Blocks**: None
|
|
- **Blocked By**: Tasks 6, 7
|
|
|
|
**References**:
|
|
- `src/components/RightPanel.tsx:143-163` - Existing FilesTab to update
|
|
- `src/services/sshDiscovery.ts:46-59` - SSH exec pattern
|
|
- Docker inspect format: `{{json .Mounts}}`
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `GET /api/files/:host/:container` endpoint exists
|
|
- [ ] FilesTab fetches and displays volume mounts
|
|
- [ ] Shows "No volumes" when container has none
|
|
- [ ] Shows "Loading..." during fetch
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Files tab shows volume mounts
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click on a service node with volumes (e.g., jellyfin)
|
|
3. Click "Files" tab in right panel
|
|
Expected Result: List of volume mounts with source and destination
|
|
Evidence: .sisyphus/evidence/task-9-files-display.png
|
|
|
|
Scenario: Files API returns mount data
|
|
Tool: Bash (curl)
|
|
Steps:
|
|
1. curl http://localhost:3001/api/files/ubuntu/jellyfin
|
|
Expected Result: JSON array with volume objects
|
|
Evidence: .sisyphus/evidence/task-9-files-api.json
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ui): add real volume data to Files tab`
|
|
- Files: `server/routes/files.ts`, `src/components/RightPanel.tsx`, `server/index.ts`
|
|
|
|
---
|
|
|
|
- [ ] 10. **Implement Usage Tab with Real Data**
|
|
|
|
**What to do**:
|
|
- Create `server/routes/stats.ts` with Express router
|
|
- Implement `GET /api/stats/:host/:container` endpoint:
|
|
- SSH to host
|
|
- Run `docker stats <container> --no-stream --format '{"cpu":"{{.CPUPerc}}","mem":"{{.MemPerc}}","net":"{{.NetIO}}"}'`
|
|
- Parse output (remove % signs, convert to numbers)
|
|
- Return StatsResponse with cpu, memory, network values
|
|
- Update `RightPanel.tsx` UsageTab:
|
|
- Fetch stats from API when service node selected
|
|
- Display progress bars for CPU and Memory
|
|
- Show network I/O as text
|
|
- Auto-refresh stats every 5 seconds while tab is visible
|
|
- Show "N/A" for stopped containers
|
|
|
|
**Must NOT do**:
|
|
- Do NOT add historical charts (live only)
|
|
- Do NOT add alerting thresholds
|
|
- Do NOT store stats data
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Simple docker stats parsing and display
|
|
- **Skills**: [`system-admin`]
|
|
- `system-admin`: Understanding docker stats format
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 2 (with Tasks 8, 9, after Task 7)
|
|
- **Blocks**: None
|
|
- **Blocked By**: Tasks 6, 7
|
|
|
|
**References**:
|
|
- `src/components/RightPanel.tsx:165-209` - Existing UsageTab to update
|
|
- `src/services/sshDiscovery.ts:46-59` - SSH exec pattern
|
|
- Docker stats format: `--format` flag with Go templates
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] `GET /api/stats/:host/:container` endpoint exists
|
|
- [ ] UsageTab fetches and displays live stats
|
|
- [ ] Stats auto-refresh every 5 seconds
|
|
- [ ] Shows "N/A" for stopped containers
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Usage tab shows live docker stats
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click on a running service node
|
|
3. Click "Usage" tab in right panel
|
|
4. Wait 5 seconds for refresh
|
|
Expected Result: CPU/Memory bars update with real values
|
|
Evidence: .sisyphus/evidence/task-10-usage-live.png
|
|
|
|
Scenario: Stats API returns numeric data
|
|
Tool: Bash (curl)
|
|
Steps:
|
|
1. curl http://localhost:3001/api/stats/ubuntu/traefik
|
|
Expected Result: JSON with cpu, memory as numbers (0-100)
|
|
Evidence: .sisyphus/evidence/task-10-stats-api.json
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ui): add live docker stats to Usage tab`
|
|
- Files: `server/routes/stats.ts`, `src/components/RightPanel.tsx`, `server/index.ts`
|
|
|
|
---
|
|
|
|
- [ ] 11. **Implement Configurable Poll Interval**
|
|
|
|
**What to do**:
|
|
- Add `pollInterval: number` to topologyStore (default: 30000ms)
|
|
- Add `setPollInterval(ms: number)` action
|
|
- Update `App.tsx` to use store's pollInterval instead of hardcoded constant
|
|
- Add settings dropdown in Header (gear icon) with poll interval options:
|
|
- 10 seconds, 30 seconds, 1 minute, 5 minutes
|
|
- Update Footer countdown to reflect new interval
|
|
- Include pollInterval in localStorage persistence
|
|
|
|
**Must NOT do**:
|
|
- Do NOT add freeform input (use preset options only)
|
|
- Do NOT add "pause polling" button (keep simple)
|
|
- Do NOT show interval in seconds (human-friendly format)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Simple dropdown and store integration
|
|
- **Skills**: [`frontend-ui-ux`]
|
|
- `frontend-ui-ux`: Settings UI design
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 3 (with Tasks 12, 13, 14)
|
|
- **Blocks**: None
|
|
- **Blocked By**: Task 7
|
|
|
|
**References**:
|
|
- `src/App.tsx:15` - POLLING_INTERVAL_MS constant to replace
|
|
- `src/App.tsx:78-81` - setInterval to make dynamic
|
|
- `src/components/Header.tsx:78-89` - Dropdown pattern to reference
|
|
- `src/store/topologyStore.ts` - Add pollInterval state
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] pollInterval in store with 30s default
|
|
- [ ] Settings dropdown in Header with 4 options
|
|
- [ ] Polling interval updates when changed
|
|
- [ ] Footer countdown reflects new interval
|
|
- [ ] Setting persists across refresh
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Poll interval can be changed
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click settings icon in Header
|
|
3. Select "5 minutes" option
|
|
4. Check footer countdown shows "299s" → "298s"
|
|
Expected Result: Countdown reflects 5-minute interval
|
|
Evidence: .sisyphus/evidence/task-11-poll-5min.png
|
|
|
|
Scenario: Poll interval persists
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Set poll interval to "1 minute"
|
|
2. Refresh page
|
|
3. Check footer countdown
|
|
Expected Result: Countdown starts at 59s (1 minute)
|
|
Evidence: .sisyphus/evidence/task-11-poll-persist.png
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ui): add configurable poll interval`
|
|
- Files: `src/store/topologyStore.ts`, `src/App.tsx`, `src/components/Header.tsx`
|
|
|
|
---
|
|
|
|
- [ ] 12. **Implement Command Palette**
|
|
|
|
**What to do**:
|
|
- Create `src/components/CommandPalette.tsx` modal component
|
|
- Add `commandPaletteOpen: boolean` to topologyStore
|
|
- Implement Cmd+K keyboard shortcut to toggle palette
|
|
- Palette shows searchable list of commands:
|
|
- "Refresh discovery" → triggers refresh
|
|
- "Toggle [type] filter" → toggles each type filter
|
|
- "Set view: Full/Network/Hosts/Services/Files" → changes view
|
|
- "Set orientation: LR/TB" → changes orientation
|
|
- Fuzzy search filters commands as user types
|
|
- Arrow keys navigate, Enter executes, Escape closes
|
|
- Click outside or Escape to close
|
|
|
|
**Must NOT do**:
|
|
- Do NOT add command history
|
|
- Do NOT add custom commands
|
|
- Do NOT add macro recording
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `visual-engineering`
|
|
- Reason: Modal UI with keyboard navigation, fuzzy search
|
|
- **Skills**: [`frontend-ui-ux`]
|
|
- `frontend-ui-ux`: Modal design, keyboard interactions
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 3 (with Tasks 11, 13, 14)
|
|
- **Blocks**: None
|
|
- **Blocked By**: Tasks 3, 4 (needs filter state)
|
|
|
|
**References**:
|
|
- `src/components/Header.tsx` - View mode and filter actions to wire
|
|
- `src/store/topologyStore.ts` - Actions to call from commands
|
|
- Cmd+K pattern: Standard in VS Code, Linear, etc.
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] Cmd+K opens command palette modal
|
|
- [ ] Fuzzy search filters commands
|
|
- [ ] Arrow keys navigate, Enter executes
|
|
- [ ] Escape or click-outside closes
|
|
- [ ] Commands execute correct actions
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Command palette opens with Cmd+K
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Press Meta+K (or Ctrl+K on Windows)
|
|
Expected Result: Modal appears with search input focused
|
|
Evidence: .sisyphus/evidence/task-12-palette-open.png
|
|
|
|
Scenario: Search and execute command
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Open command palette
|
|
2. Type "network"
|
|
3. Press Enter
|
|
Expected Result: View mode changes to Network
|
|
Evidence: .sisyphus/evidence/task-12-palette-execute.png
|
|
|
|
Scenario: Escape closes palette
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Open command palette
|
|
2. Press Escape
|
|
Expected Result: Modal closes, focus returns to graph
|
|
Evidence: .sisyphus/evidence/task-12-palette-close.png
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ux): add command palette with Cmd+K`
|
|
- Files: `src/components/CommandPalette.tsx`, `src/App.tsx`, `src/store/topologyStore.ts`
|
|
|
|
---
|
|
|
|
- [ ] 13. **Implement Stale Data Warning**
|
|
|
|
**What to do**:
|
|
- Add `consecutiveFailures: number` and `lastSuccessfulDiscovery: Date | null` to topologyStore
|
|
- Increment consecutiveFailures on API error, reset to 0 on success
|
|
- Create `src/components/StaleWarning.tsx` banner component
|
|
- Show banner when consecutiveFailures >= 3
|
|
- Banner shows: "Data may be stale - Last successful: [timestamp]"
|
|
- Banner has dismiss button (X) that temporarily hides it
|
|
- Banner auto-dismisses on successful discovery
|
|
- Style: Yellow/warning background, at top of main content area
|
|
|
|
**Must NOT do**:
|
|
- Do NOT add retry button (use main Refresh button)
|
|
- Do NOT show detailed error messages
|
|
- Do NOT add notification sound
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `quick`
|
|
- Reason: Simple conditional banner with dismiss
|
|
- **Skills**: [`frontend-ui-ux`]
|
|
- `frontend-ui-ux`: Warning banner styling
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 3 (with Tasks 11, 12, 14)
|
|
- **Blocks**: None
|
|
- **Blocked By**: Task 7
|
|
|
|
**References**:
|
|
- `src/App.tsx:63-69` - Error handling in loadData to update
|
|
- `src/store/topologyStore.ts` - Add failure tracking state
|
|
- Warning banner pattern: Common UI pattern
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] consecutiveFailures tracked in store
|
|
- [ ] Banner shows after 3 consecutive failures
|
|
- [ ] Banner shows last successful timestamp
|
|
- [ ] Dismiss button hides banner temporarily
|
|
- [ ] Banner auto-dismisses on success
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Warning appears after failures
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Stop backend server
|
|
2. Navigate to http://localhost:3000
|
|
3. Wait for 3 discovery attempts (90 seconds or speed up polling)
|
|
Expected Result: Yellow warning banner visible at top
|
|
Evidence: .sisyphus/evidence/task-13-stale-warning.png
|
|
|
|
Scenario: Warning dismisses on success
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Show warning banner (3+ failures)
|
|
2. Start backend server
|
|
3. Wait for next discovery
|
|
Expected Result: Banner disappears automatically
|
|
Evidence: .sisyphus/evidence/task-13-warning-dismiss.png
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ux): add stale data warning banner`
|
|
- Files: `src/components/StaleWarning.tsx`, `src/App.tsx`, `src/store/topologyStore.ts`
|
|
|
|
---
|
|
|
|
- [ ] 14. **Implement Path Highlighting**
|
|
|
|
**What to do**:
|
|
- Add `highlightPath: string[]` to topologyStore (array of node IDs from root to selected)
|
|
- Add `setHighlightPath(ids: string[])` action
|
|
- When node selected, calculate path from gateway to that node:
|
|
- Walk up parentId chain from selected node to root
|
|
- Store all ancestor IDs in highlightPath
|
|
- Update `TopologyGraph.tsx` to style highlighted nodes:
|
|
- Highlighted nodes get brighter border (2px solid, accent color)
|
|
- Non-highlighted nodes get dimmed opacity (0.5)
|
|
- Highlighted edges get accent color
|
|
- Clear highlightPath when clicking background (deselect)
|
|
|
|
**Must NOT do**:
|
|
- Do NOT animate the highlighting
|
|
- Do NOT add "highlight path" button (automatic on select)
|
|
- Do NOT highlight children (only ancestors)
|
|
|
|
**Recommended Agent Profile**:
|
|
- **Category**: `visual-engineering`
|
|
- Reason: Graph styling with conditional classes
|
|
- **Skills**: [`frontend-ui-ux`]
|
|
- `frontend-ui-ux`: Visual highlighting design
|
|
|
|
**Parallelization**:
|
|
- **Can Run In Parallel**: YES
|
|
- **Parallel Group**: Wave 3 (with Tasks 11, 12, 13)
|
|
- **Blocks**: None
|
|
- **Blocked By**: Task 7
|
|
|
|
**References**:
|
|
- `src/components/Graph/TopologyGraph.tsx` - Node styling to update
|
|
- `src/store/topologyStore.ts:71-77` - getChildNodes pattern to reference
|
|
- Node data has `parentId` field for walking ancestors
|
|
|
|
**Acceptance Criteria**:
|
|
- [ ] highlightPath calculated on node selection
|
|
- [ ] Ancestor nodes have highlighted style
|
|
- [ ] Non-ancestor nodes dimmed
|
|
- [ ] Edges to ancestors highlighted
|
|
- [ ] Clicking background clears highlight
|
|
|
|
**QA Scenarios**:
|
|
```
|
|
Scenario: Path highlighting shows ancestors
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Navigate to http://localhost:3000
|
|
2. Click on a container node (e.g., ubuntu-traefik)
|
|
3. Check visual state of ancestor nodes (ubuntu, vlan-50, gateway)
|
|
Expected Result: Ancestors have bright border, others dimmed
|
|
Evidence: .sisyphus/evidence/task-14-path-highlight.png
|
|
|
|
Scenario: Clicking background clears highlight
|
|
Tool: Playwright
|
|
Steps:
|
|
1. Select a node (path highlighted)
|
|
2. Click on empty graph background
|
|
Expected Result: All nodes return to normal opacity
|
|
Evidence: .sisyphus/evidence/task-14-highlight-clear.png
|
|
```
|
|
|
|
**Commit**: YES
|
|
- Message: `feat(ux): add path highlighting for selected nodes`
|
|
- Files: `src/components/Graph/TopologyGraph.tsx`, `src/store/topologyStore.ts`
|
|
|
|
---
|
|
|
|
## Final Verification Wave
|
|
|
|
- [ ] F1. **Plan Compliance Audit** — `oracle`
|
|
Read the plan end-to-end. For each "Must Have": verify implementation exists. For each "Must NOT Have": search codebase for forbidden patterns. Check evidence files exist in .sisyphus/evidence/. Compare deliverables against plan.
|
|
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
|
|
|
|
- [ ] F2. **Code Quality Review** — `unspecified-high`
|
|
Run `tsc --noEmit` + `bun run lint` + `bun test`. Review all changed files for: `as any`/`@ts-ignore`, empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction.
|
|
Output: `Build [PASS/FAIL] | Lint [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT`
|
|
|
|
- [ ] F3. **Real Manual QA** — `unspecified-high` (+ `playwright` skill)
|
|
Start from clean state. Execute EVERY QA scenario from EVERY task — follow exact steps, capture evidence. Test cross-task integration. Test edge cases: empty state, invalid input. Save to `.sisyphus/evidence/final-qa/`.
|
|
Output: `Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT`
|
|
|
|
- [ ] F4. **Scope Fidelity Check** — `deep`
|
|
For each task: read "What to do", read actual diff. Verify 1:1 — everything in spec was built, nothing beyond spec. Check "Must NOT do" compliance. Detect cross-task contamination.
|
|
Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT`
|
|
|
|
---
|
|
|
|
## Commit Strategy
|
|
|
|
- **Wave 1**: `feat(backend): add Express server foundation with types`
|
|
- **Wave 1**: `feat(ui): add type and status filters, localStorage persistence`
|
|
- **Wave 2**: `feat(api): implement SSH discovery and data endpoints`
|
|
- **Wave 2**: `feat(ui): wire real data to Config/Files/Usage tabs`
|
|
- **Wave 3**: `feat(ux): add command palette, stale warning, path highlighting`
|
|
- **Final**: `feat: complete remaining features implementation`
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
### Verification Commands
|
|
```bash
|
|
# Backend health check
|
|
curl http://localhost:3001/api/health
|
|
# Expected: {"status":"ok"}
|
|
|
|
# SSH discovery
|
|
curl http://localhost:3001/api/discover | jq '.hosts | length'
|
|
# Expected: 6 (number of configured hosts)
|
|
|
|
# Config retrieval
|
|
curl http://localhost:3001/api/config/ubuntu/traefik
|
|
# Expected: YAML content of docker-compose.yml
|
|
|
|
# Frontend build
|
|
cd /home/christopher/homelab-topology && bun run build
|
|
# Expected: Build succeeds with no errors
|
|
|
|
# Type check
|
|
bun run tsc --noEmit
|
|
# Expected: No type errors
|
|
```
|
|
|
|
### Final Checklist
|
|
- [ ] All "Must Have" features present
|
|
- [ ] All "Must NOT Have" constraints respected
|
|
- [ ] No SSH credentials in localStorage
|
|
- [ ] Backend API functional on port 3001
|
|
- [ ] Frontend connects to backend
|
|
- [ ] All filters functional and persistent
|
|
- [ ] Command palette works with Cmd+K
|
|
- [ ] Stale warning appears on failures
|
|
- [ ] Path highlighting shows ancestors
|