chore: submodule refs update + CI workflow + plan archive
This commit is contained in:
@@ -0,0 +1,689 @@
|
||||
# NexusGuard Config Architecture
|
||||
|
||||
## TL;DR
|
||||
|
||||
> Unified config system for non-Docker deployment: `/etc/nexusguard/nexusguard.conf` (shell-sourceable), install/uninstall scripts, systemd service, and nginx runtime config injection for dashboard.
|
||||
>
|
||||
> **Deliverables**:
|
||||
> - Server-core config file loader
|
||||
> - Dashboard runtime config via nginx
|
||||
> - Install/uninstall shell scripts
|
||||
> - Systemd service file
|
||||
> - Nginx config template
|
||||
>
|
||||
> **Estimated Effort**: Medium
|
||||
> **Parallel Execution**: YES - 3 waves
|
||||
> **Critical Path**: Config loader → Dashboard changes → Install scripts → Testing
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request
|
||||
User wants unified config architecture for non-Docker deployment. Currently config is split:
|
||||
- Server-core: env vars from docker-compose `.env`
|
||||
- Dashboard: `VITE_API_BASE_URL` baked at build time
|
||||
- Device-agent: CLI args (no change needed)
|
||||
|
||||
### Interview Summary
|
||||
**Key Discussions**:
|
||||
- Config format: Shell-sourceable (export KEY=VALUE)
|
||||
- Config location: `/etc/nexusguard/nexusguard.conf`
|
||||
- Structure: Server-core + dashboard-ui share ONE config file
|
||||
- Device-agent: No config file (uses CLI args)
|
||||
- Dashboard: Runtime config via nginx template (window.__CONFIG__)
|
||||
- Docker: Keep env vars unchanged
|
||||
- Non-Docker: Read from nexusguard.conf
|
||||
- Install: Shell script (nexusguard-install.sh)
|
||||
- Uninstall: Shell script (nexusguard-uninstall.sh)
|
||||
- Systemd: Service file for server-core
|
||||
|
||||
**Research Findings**:
|
||||
- Server-core config loading: `internal/config/config.go` reads env vars via `os.Getenv()`
|
||||
- Dashboard config: `src/services/api.ts` uses `import.meta.env.VITE_API_BASE_URL`
|
||||
- Existing patterns: Device-agent's `install_agent.sh` and `sys-bridge.service`
|
||||
- Real server: 172.20.8.191 for testing
|
||||
|
||||
### Metis Review
|
||||
**Identified Gaps** (addressed):
|
||||
- Config file path should be overridable via `NEXUSGUARD_CONF` env var
|
||||
- nginx can't source bash files → use `envsubst` with template
|
||||
- Secrets in conf file need `chmod 600` permissions
|
||||
- Config loading order: conf file → env vars → defaults
|
||||
- Install script must be idempotent
|
||||
- Dashboard `window.__CONFIG__` timing: script tag MUST appear before Vue bundle
|
||||
- Edge cases: empty values, spaces, special characters, BOM, Windows line endings
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
Implement unified config system for non-Docker NexusGuard deployment with `/etc/nexusguard/nexusguard.conf`, install/uninstall scripts, and nginx runtime config injection.
|
||||
|
||||
### Concrete Deliverables
|
||||
- `apps/server-core/internal/config/config_loader.go` - Config file loader
|
||||
- `apps/server-core/internal/config/config_test.go` - Unit tests
|
||||
- `apps/server-core/main.go` - Updated to call config loader
|
||||
- `apps/dashboard-ui/src/services/api.ts` - Runtime config support
|
||||
- `apps/dashboard-ui/nginx.conf.template` - Nginx config template
|
||||
- `nexusguard-install.sh` - Install script
|
||||
- `nexusguard-uninstall.sh` - Uninstall script
|
||||
- `apps/server-core/nexusguard-server.service` - Systemd service file
|
||||
|
||||
### Definition of Done
|
||||
- [x] Server-core reads config from `/etc/nexusguard/nexusguard.conf`
|
||||
- [x] Dashboard reads runtime config from nginx-injected `window.__CONFIG__`
|
||||
- [x] Install script copies binaries, creates config, sets up systemd, configures nginx
|
||||
- [x] Uninstall script stops service, removes files, reloads nginx
|
||||
- [x] All unit tests pass
|
||||
- [x] Tested on real server (172.20.8.191)
|
||||
|
||||
### Must Have
|
||||
- Config file loading with fallback to env vars
|
||||
- Dashboard runtime config injection via nginx
|
||||
- Idempotent install/uninstall scripts
|
||||
- Secure config file permissions (chmod 600)
|
||||
- Systemd service with restart on failure
|
||||
|
||||
### Must NOT Have (Guardrails)
|
||||
- **NEVER** modify Docker behavior (docker-compose.yml, Dockerfiles stay unchanged)
|
||||
- **NEVER** touch device-agent (uses CLI args, not config file)
|
||||
- **NEVER** add PostgreSQL/Redis installation to install script
|
||||
- **NEVER** add TLS/HTTPS setup to install script
|
||||
- **NEVER** add auto-reload on config changes
|
||||
- **NEVER** add config file validation (schema)
|
||||
- **NEVER** add log rotation or monitoring
|
||||
- **NEVER** add multi-server deployment support
|
||||
- **NEVER** add `nexusguard-ctl` management CLI
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
> **ZERO HUMAN INTERVENTION** - ALL verification is agent-executed. No exceptions.
|
||||
|
||||
### Test Decision
|
||||
- **Infrastructure exists**: YES (Go tests for server-core, npm for dashboard)
|
||||
- **Automated tests**: YES (Tests-after)
|
||||
- **Framework**: Go testing (server-core), no framework for dashboard (manual verification)
|
||||
|
||||
### QA Policy
|
||||
Every task MUST include agent-executed QA scenarios.
|
||||
Evidence saved to `.sisyphus/evidence/task-{N}-{scenario-slug}.{ext}`.
|
||||
|
||||
- **Go code**: Use `go test` - Run tests, assert pass
|
||||
- **Shell scripts**: Use `bash` - Run script, verify exit code and file creation
|
||||
- **Config loading**: Use Go test with temp files
|
||||
- **Nginx config**: Use `nginx -t` to validate syntax
|
||||
- **Real server**: SSH to 172.20.8.191 and test
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
### Parallel Execution Waves
|
||||
|
||||
```
|
||||
Wave 1 (Start Immediately - foundation):
|
||||
├── Task 1: Config file loader for server-core [deep]
|
||||
├── Task 2: Dashboard runtime config support [quick]
|
||||
└── Task 3: Nginx config template [quick]
|
||||
|
||||
Wave 2 (After Wave 1 - scripts):
|
||||
├── Task 4: Install script (depends: 1, 2, 3) [deep]
|
||||
├── Task 5: Uninstall script (depends: 4) [quick]
|
||||
└── Task 6: Systemd service file (depends: 1) [quick]
|
||||
|
||||
Wave FINAL (After ALL tasks):
|
||||
├── Task F1: Plan compliance audit (oracle)
|
||||
├── Task F2: Code quality review (unspecified-high)
|
||||
├── Task F3: Real manual QA on server 172.20.8.191 (unspecified-high)
|
||||
└── Task F4: Scope fidelity check (deep)
|
||||
```
|
||||
|
||||
### Dependency Matrix
|
||||
|
||||
| Task | Depends On | Blocks |
|
||||
|------|-----------|--------|
|
||||
| 1 | None | 4, 6 |
|
||||
| 2 | None | 4 |
|
||||
| 3 | None | 4 |
|
||||
| 4 | 1, 2, 3 | F1-F4 |
|
||||
| 5 | 4 | F1-F4 |
|
||||
| 6 | 1 | F1-F4 |
|
||||
|
||||
### Agent Dispatch Summary
|
||||
|
||||
- **Wave 1**: 3 tasks - T1 → `deep`, T2 → `quick`, T3 → `quick`
|
||||
- **Wave 2**: 3 tasks - T4 → `deep`, T5 → `quick`, T6 → `quick`
|
||||
- **FINAL**: 4 tasks - F1 → `oracle`, F2 → `unspecified-high`, F3 → `unspecified-high`, F4 → `deep`
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] 1. Config file loader for server-core
|
||||
|
||||
**What to do**:
|
||||
- Create `apps/server-core/internal/config/config_loader.go`
|
||||
- Implement `LoadConfFile(path string)` function
|
||||
- Parse shell-sourceable format (export KEY=VALUE)
|
||||
- Handle edge cases: comments (#), empty lines, spaces, Windows line endings (\r\n), BOM
|
||||
- Skip malformed lines (no = sign)
|
||||
- Trim whitespace around keys and values
|
||||
- Call `os.Setenv()` for each valid key
|
||||
- Return error if file doesn't exist (but don't fatal)
|
||||
- Support path override via `NEXUSGUARD_CONF` env var
|
||||
- Default path: `/etc/nexusguard/nexusguard.conf`
|
||||
|
||||
**Must NOT do**:
|
||||
- Don't modify existing `config.Load()` function
|
||||
- Don't add external dependencies (use stdlib only)
|
||||
- Don't make config loading fatal (log warning if file missing)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `deep`
|
||||
- **Skills**: []
|
||||
- **Reason**: Go code, requires understanding of existing config pattern
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES
|
||||
- **Parallel Group**: Wave 1 (with Tasks 2, 3)
|
||||
- **Blocks**: Tasks 4, 6
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `apps/server-core/internal/config/config.go:25-55` - Existing config loading pattern
|
||||
- `apps/server-core/main.go:176` - Where config.Load() is called
|
||||
- `apps/device-agent/scripts/install_agent.sh` - Shell script pattern to follow
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] File created: `apps/server-core/internal/config/config_loader.go`
|
||||
- [x] Function `LoadConfFile(path string) error` exists
|
||||
- [x] Parses `export KEY=VALUE` format
|
||||
- [x] Skips comments (#) and empty lines
|
||||
- [x] Handles Windows line endings (\r\n)
|
||||
- [x] Trims whitespace around keys and values
|
||||
- [x] Calls `os.Setenv()` for each valid key
|
||||
- [x] Returns error for missing file (non-fatal)
|
||||
- [x] Supports `NEXUSGUARD_CONF` env var override
|
||||
|
||||
**QA Scenarios**:
|
||||
|
||||
```
|
||||
Scenario: Parse valid config file
|
||||
Tool: Bash (go test)
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Create temp file with: export JWT_SECRET=test123\nexport SERVER_SALT=salt456\nexport DB_HOST=customhost
|
||||
2. Call LoadConfFile(tempPath)
|
||||
3. Assert os.Getenv("JWT_SECRET") == "test123"
|
||||
4. Assert os.Getenv("DB_HOST") == "customhost"
|
||||
Expected Result: All values set correctly
|
||||
Evidence: .sisyphus/evidence/task-1-parse-valid.txt
|
||||
|
||||
Scenario: Handle missing config file
|
||||
Tool: Bash (go test)
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Call LoadConfFile("/nonexistent/path")
|
||||
2. Assert error is returned
|
||||
3. Assert no panic or fatal
|
||||
Expected Result: Error returned, process continues
|
||||
Evidence: .sisyphus/evidence/task-1-missing-file.txt
|
||||
|
||||
Scenario: Skip malformed lines
|
||||
Tool: Bash (go test)
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Create temp file with: export VALID=yes\nINVALID_LINE\nexport ALSO_VALID=ok
|
||||
2. Call LoadConfFile(tempPath)
|
||||
3. Assert os.Getenv("VALID") == "yes"
|
||||
4. Assert os.Getenv("ALSO_VALID") == "ok"
|
||||
Expected Result: Malformed line skipped
|
||||
Evidence: .sisyphus/evidence/task-1-malformed-lines.txt
|
||||
|
||||
Scenario: Handle Windows line endings
|
||||
Tool: Bash (go test)
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Create temp file with: export KEY1=val1\r\nexport KEY2=val2\r\n
|
||||
2. Call LoadConfFile(tempPath)
|
||||
3. Assert os.Getenv("KEY1") == "val1"
|
||||
4. Assert os.Getenv("KEY2") == "val2"
|
||||
Expected Result: \r\n handled correctly
|
||||
Evidence: .sisyphus/evidence/task-1-windows-endings.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(server-core): add config file loader for /etc/nexusguard/nexusguard.conf`
|
||||
- Files: `apps/server-core/internal/config/config_loader.go`
|
||||
- Pre-commit: `go test ./internal/config/... -v`
|
||||
|
||||
---
|
||||
|
||||
- [x] 2. Dashboard runtime config support
|
||||
|
||||
**What to do**:
|
||||
- Modify `apps/dashboard-ui/src/services/api.ts`
|
||||
- Add TypeScript type declaration for `window.__CONFIG__`
|
||||
- Read `window.__CONFIG__?.apiBaseUrl` with fallback to `import.meta.env.VITE_API_BASE_URL`
|
||||
- Keep Docker compatibility (VITE_API_BASE_URL still works)
|
||||
- Add comment explaining runtime config injection
|
||||
|
||||
**Must NOT do**:
|
||||
- Don't remove VITE_API_BASE_URL support (Docker compatibility)
|
||||
- Don't change build process
|
||||
- Don't add external dependencies
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: []
|
||||
- **Reason**: Simple TypeScript change, single file
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES
|
||||
- **Parallel Group**: Wave 1 (with Tasks 1, 3)
|
||||
- **Blocks**: Task 4
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `apps/dashboard-ui/src/services/api.ts:1-10` - Current API client setup
|
||||
- `apps/dashboard-ui/nginx.conf` - Current nginx config
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] File modified: `apps/dashboard-ui/src/services/api.ts`
|
||||
- [x] `window.__CONFIG__` type declared
|
||||
- [x] Runtime config read with fallback to VITE_API_BASE_URL
|
||||
- [x] TypeScript compiles without errors
|
||||
|
||||
**QA Scenarios**:
|
||||
|
||||
```
|
||||
Scenario: Runtime config override
|
||||
Tool: Bash (manual verification)
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Read api.ts file
|
||||
2. Verify window.__CONFIG__?.apiBaseUrl is checked first
|
||||
3. Verify fallback to import.meta.env.VITE_API_BASE_URL
|
||||
Expected Result: Runtime config takes precedence
|
||||
Evidence: .sisyphus/evidence/task-2-runtime-config.txt
|
||||
|
||||
Scenario: Docker compatibility
|
||||
Tool: Bash (manual verification)
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Read api.ts file
|
||||
2. Verify VITE_API_BASE_URL fallback exists
|
||||
Expected Result: Docker builds still work
|
||||
Evidence: .sisyphus/evidence/task-2-docker-compat.txt
|
||||
```
|
||||
|
||||
**Commit**: YES (groups with Task 1)
|
||||
- Message: `feat(dashboard): add runtime config support via window.__CONFIG__`
|
||||
- Files: `apps/dashboard-ui/src/services/api.ts`
|
||||
|
||||
---
|
||||
|
||||
- [x] 3. Nginx config template
|
||||
|
||||
**What to do**:
|
||||
- Create `apps/dashboard-ui/nginx.conf.template`
|
||||
- Use `envsubst` placeholders for runtime config injection
|
||||
- Proxy `/api/` to `http://127.0.0.1:${API_PORT}`
|
||||
- Serve static SPA from `/usr/share/nexusguard/dashboard`
|
||||
- Inject `window.__CONFIG__` script tag before Vue bundle
|
||||
- Handle SPA fallback (try_files $uri $uri/ /index.html)
|
||||
- Listen on port 80 (or configurable)
|
||||
|
||||
**Must NOT do**:
|
||||
- Don't add TLS/HTTPS configuration
|
||||
- Don't hardcode API_PORT (use envsubst)
|
||||
- Don't modify existing Docker nginx.conf
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: []
|
||||
- **Reason**: Simple nginx config template
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES
|
||||
- **Parallel Group**: Wave 1 (with Tasks 1, 2)
|
||||
- **Blocks**: Task 4
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `apps/dashboard-ui/nginx.conf` - Current Docker nginx config
|
||||
- `apps/server-core/main.go:356` - API port default (8080)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] File created: `apps/dashboard-ui/nginx.conf.template`
|
||||
- [x] `envsubst` placeholders for `${API_PORT}`, `${API_BASE_URL}`
|
||||
- [x] `window.__CONFIG__` injection via `sub_filter` or template
|
||||
- [x] SPA fallback configured
|
||||
- [x] `nginx -t` validates syntax
|
||||
|
||||
**QA Scenarios**:
|
||||
|
||||
```
|
||||
Scenario: Nginx config syntax
|
||||
Tool: Bash
|
||||
Preconditions: nginx installed
|
||||
Steps:
|
||||
1. Run: nginx -t -c /path/to/nginx.conf.template
|
||||
2. Assert exit code 0
|
||||
Expected Result: Config syntax valid
|
||||
Evidence: .sisyphus/evidence/task-3-nginx-syntax.txt
|
||||
|
||||
Scenario: Runtime config injection
|
||||
Tool: Bash (manual verification)
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Read nginx.conf.template
|
||||
2. Verify window.__CONFIG__ injection mechanism exists
|
||||
3. Verify it appears before Vue bundle script
|
||||
Expected Result: Config injected correctly
|
||||
Evidence: .sisyphus/evidence/task-3-config-injection.txt
|
||||
```
|
||||
|
||||
**Commit**: YES (groups with Tasks 1, 2)
|
||||
- Message: `feat(dashboard): add nginx config template for runtime config injection`
|
||||
- Files: `apps/dashboard-ui/nginx.conf.template`
|
||||
|
||||
---
|
||||
|
||||
- [x] 4. Install script
|
||||
|
||||
**What to do**:
|
||||
- Create `nexusguard-install.sh` at project root
|
||||
- Parse arguments (--help, --server-port, --web-port)
|
||||
- Check dependencies (nginx, systemctl)
|
||||
- Copy server-core binary to `/usr/local/bin/`
|
||||
- Copy dashboard dist to `/usr/share/nexusguard/dashboard/`
|
||||
- Create `/etc/nexusguard/nexusguard.conf` with template values
|
||||
- Set permissions: `chmod 600 /etc/nexusguard/nexusguard.conf`
|
||||
- Create systemd service file at `/etc/systemd/system/nexusguard-server.service`
|
||||
- Create nginx config at `/etc/nginx/conf.d/nexusguard.conf`
|
||||
- Enable and start service
|
||||
- Print access URL
|
||||
- Handle idempotency (check existing service, skip or overwrite gracefully)
|
||||
|
||||
**Must NOT do**:
|
||||
- Don't install PostgreSQL or Redis
|
||||
- Don't install nginx (assume pre-installed)
|
||||
- Don't configure TLS/HTTPS
|
||||
- Don't build from source (expect pre-built binaries)
|
||||
- Don't modify Docker behavior
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `deep`
|
||||
- **Skills**: []
|
||||
- **Reason**: Complex shell script with multiple system interactions
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO
|
||||
- **Parallel Group**: Wave 2 (sequential after Wave 1)
|
||||
- **Blocks**: Tasks 5, F1-F4
|
||||
- **Blocked By**: Tasks 1, 2, 3
|
||||
|
||||
**References**:
|
||||
- `apps/device-agent/scripts/install_agent.sh` - Pattern to follow
|
||||
- `apps/server-core/nexusguard-server.service` - Systemd template
|
||||
- `apps/dashboard-ui/nginx.conf.template` - Nginx config to copy
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] File created: `nexusguard-install.sh`
|
||||
- [x] `--help` flag shows usage
|
||||
- [x] Creates `/etc/nexusguard/nexusguard.conf` with chmod 600
|
||||
- [x] Creates systemd service file
|
||||
- [x] Creates nginx config
|
||||
- [x] Enables and starts service
|
||||
- [x] Idempotent (safe to run twice)
|
||||
|
||||
**QA Scenarios**:
|
||||
|
||||
```
|
||||
Scenario: Install --help
|
||||
Tool: Bash
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Run: bash nexusguard-install.sh --help
|
||||
2. Assert exit code 0
|
||||
3. Assert usage text displayed
|
||||
Expected Result: Help text shown
|
||||
Evidence: .sisyphus/evidence/task-4-install-help.txt
|
||||
|
||||
Scenario: Install creates config file
|
||||
Tool: Bash
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Run: bash nexusguard-install.sh
|
||||
2. Assert /etc/nexusguard/nexusguard.conf exists
|
||||
3. Assert file permissions are 600
|
||||
4. Assert file contains export statements
|
||||
Expected Result: Config file created securely
|
||||
Evidence: .sisyphus/evidence/task-4-config-created.txt
|
||||
|
||||
Scenario: Install creates systemd service
|
||||
Tool: Bash
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Run: bash nexusguard-install.sh
|
||||
2. Assert /etc/systemd/system/nexusguard-server.service exists
|
||||
3. Assert service is enabled
|
||||
Expected Result: Systemd service configured
|
||||
Evidence: .sisyphus/evidence/task-4-systemd-created.txt
|
||||
|
||||
Scenario: Install idempotency
|
||||
Tool: Bash
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Run: bash nexusguard-install.sh
|
||||
2. Run: bash nexusguard-install.sh (again)
|
||||
3. Assert no errors
|
||||
4. Assert service still running
|
||||
Expected Result: Safe to run multiple times
|
||||
Evidence: .sisyphus/evidence/task-4-idempotency.txt
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat: add nexusguard-install.sh for non-Docker deployment`
|
||||
- Files: `nexusguard-install.sh`
|
||||
- Pre-commit: `bash nexusguard-install.sh --help`
|
||||
|
||||
---
|
||||
|
||||
- [x] 5. Uninstall script
|
||||
|
||||
**What to do**:
|
||||
- Create `nexusguard-uninstall.sh` at project root
|
||||
- Stop and disable nexusguard-server service
|
||||
- Remove `/usr/local/bin/nexusguard-server-core`
|
||||
- Remove `/usr/share/nexusguard/dashboard/`
|
||||
- Remove `/etc/nexusguard/nexusguard.conf`
|
||||
- Remove `/etc/systemd/system/nexusguard-server.service`
|
||||
- Remove `/etc/nginx/conf.d/nexusguard.conf`
|
||||
- Reload nginx
|
||||
- Reload systemd daemon
|
||||
- Print confirmation message
|
||||
|
||||
**Must NOT do**:
|
||||
- Don't remove PostgreSQL or Redis
|
||||
- Don't remove database data
|
||||
- Don't remove WireGuard state
|
||||
- Don't remove device-agent (separate concern)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: []
|
||||
- **Reason**: Simple cleanup script
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO
|
||||
- **Parallel Group**: Wave 2 (after Task 4)
|
||||
- **Blocks**: F1-F4
|
||||
- **Blocked By**: Task 4
|
||||
|
||||
**References**:
|
||||
- `nexusguard-install.sh` - Install script to reverse
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] File created: `nexusguard-uninstall.sh`
|
||||
- [x] Stops and disables service
|
||||
- [x] Removes all installed files
|
||||
- [x] Reloads nginx and systemd
|
||||
- [x] Preserves database and WireGuard state
|
||||
|
||||
**QA Scenarios**:
|
||||
|
||||
```
|
||||
Scenario: Uninstall removes files
|
||||
Tool: Bash
|
||||
Preconditions: Install script run first
|
||||
Steps:
|
||||
1. Run: bash nexusguard-uninstall.sh
|
||||
2. Assert /etc/nexusguard/nexusguard.conf does not exist
|
||||
3. Assert /etc/systemd/system/nexusguard-server.service does not exist
|
||||
4. Assert service is stopped
|
||||
Expected Result: All files removed
|
||||
Evidence: .sisyphus/evidence/task-5-uninstall-removes.txt
|
||||
|
||||
Scenario: Uninstall preserves data
|
||||
Tool: Bash
|
||||
Preconditions: Install script run first
|
||||
Steps:
|
||||
1. Run: bash nexusguard-uninstall.sh
|
||||
2. Assert PostgreSQL data still exists
|
||||
3. Assert Redis data still exists
|
||||
Expected Result: User data preserved
|
||||
Evidence: .sisyphus/evidence/task-5-uninstall-preserves.txt
|
||||
```
|
||||
|
||||
**Commit**: YES (groups with Task 4)
|
||||
- Message: `feat: add nexusguard-uninstall.sh`
|
||||
- Files: `nexusguard-uninstall.sh`
|
||||
|
||||
---
|
||||
|
||||
- [x] 6. Systemd service file
|
||||
|
||||
**What to do**:
|
||||
- Create `apps/server-core/nexusguard-server.service`
|
||||
- Use `EnvironmentFile=/etc/nexusguard/nexusguard.conf`
|
||||
- Set `Restart=always` and `RestartSec=5`
|
||||
- Run as root (needed for nftables/WireGuard)
|
||||
- Set working directory to `/usr/local/bin`
|
||||
- Add proper logging (journal)
|
||||
- Add `After=network.target postgresql.service redis.service`
|
||||
|
||||
**Must NOT do**:
|
||||
- Don't hardcode paths (use EnvironmentFile)
|
||||
- Don't add Docker-specific settings
|
||||
- Don't add resource limits (cgroup)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: []
|
||||
- **Reason**: Simple systemd unit file
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES
|
||||
- **Parallel Group**: Wave 2 (with Tasks 4, 5)
|
||||
- **Blocks**: F1-F4
|
||||
- **Blocked By**: Task 1
|
||||
|
||||
**References**:
|
||||
- `apps/device-agent/scripts/sys-bridge.service` - Systemd template
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] File created: `apps/server-core/nexusguard-server.service`
|
||||
- [x] `EnvironmentFile=/etc/nexusguard/nexusguard.conf`
|
||||
- [x] `Restart=always` and `RestartSec=5`
|
||||
- [x] Runs as root
|
||||
- [x] `After=network.target postgresql.service redis.service`
|
||||
|
||||
**QA Scenarios**:
|
||||
|
||||
```
|
||||
Scenario: Systemd service syntax
|
||||
Tool: Bash
|
||||
Preconditions: systemd installed
|
||||
Steps:
|
||||
1. Run: systemd-analyze verify nexusguard-server.service
|
||||
2. Assert exit code 0
|
||||
Expected Result: Service file valid
|
||||
Evidence: .sisyphus/evidence/task-6-systemd-syntax.txt
|
||||
|
||||
Scenario: Environment file configured
|
||||
Tool: Bash
|
||||
Preconditions: None
|
||||
Steps:
|
||||
1. Read nexusguard-server.service
|
||||
2. Assert EnvironmentFile=/etc/nexusguard/nexusguard.conf exists
|
||||
Expected Result: Config file path correct
|
||||
Evidence: .sisyphus/evidence/task-6-env-file.txt
|
||||
```
|
||||
|
||||
**Commit**: YES (groups with Tasks 4, 5)
|
||||
- Message: `feat: add systemd service file for non-Docker deployment`
|
||||
- Files: `apps/server-core/nexusguard-server.service`
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
- [x] 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.
|
||||
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
|
||||
|
||||
- [x] F2. **Code Quality Review** — `unspecified-high`
|
||||
Run `go vet`, `go test`, `npm run build`. Review all changed files for: empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction.
|
||||
Output: `Build [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT`
|
||||
|
||||
- [x] F3. **Real Manual QA on 172.20.8.191** — `unspecified-high`
|
||||
SSH to server. Run install script. Verify config file created. Verify systemd service running. Verify nginx serving dashboard. Verify runtime config injection. Test uninstall.
|
||||
Output: `Install [PASS/FAIL] | Service [RUNNING/STOPPED] | Dashboard [ACCESSIBLE/INACCESSIBLE] | VERDICT`
|
||||
|
||||
- [x] 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 was built. 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
|
||||
|
||||
- **Task 1**: `feat(server-core): add config file loader for /etc/nexusguard/nexusguard.conf`
|
||||
- **Task 2-3**: `feat(dashboard): add runtime config support and nginx template`
|
||||
- **Task 4-6**: `feat: add install/uninstall scripts and systemd service`
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Verification Commands
|
||||
```bash
|
||||
# Server-core config loading
|
||||
cd apps/server-core && go test ./internal/config/... -v # Expected: PASS
|
||||
|
||||
# Dashboard build
|
||||
cd apps/dashboard-ui && npm run build # Expected: succeeds
|
||||
|
||||
# Install script
|
||||
bash nexusguard-install.sh --help # Expected: shows usage
|
||||
|
||||
# Uninstall script
|
||||
bash nexusguard-uninstall.sh # Expected: removes files, stops service
|
||||
|
||||
# Real server test (172.20.8.191)
|
||||
ssh root@172.20.8.191 "bash nexusguard-install.sh" # Expected: success
|
||||
ssh root@172.20.8.191 "systemctl status nexusguard-server" # Expected: active (running)
|
||||
ssh root@172.20.8.191 "curl -s http://localhost/" # Expected: HTML with window.__CONFIG__
|
||||
```
|
||||
|
||||
### Final Checklist
|
||||
- [x] All "Must Have" present
|
||||
- [x] All "Must NOT Have" absent
|
||||
- [x] All tests pass
|
||||
- [x] Tested on real server (172.20.8.191)
|
||||
@@ -0,0 +1,30 @@
|
||||
# Gitea CI Build — Device Agent
|
||||
|
||||
**Created:** 2026-05-28
|
||||
**Branch:** `dev` (demo build) + `main` (production build)
|
||||
**Target:** `apps/device-agent` submodule → `nexus-device-agent` Gitea repo
|
||||
|
||||
## Context
|
||||
|
||||
Device-agent saat ini sudah punya `.gitea/workflows/build.yml` tapi belum lengkap:
|
||||
- Hanya build `linux` (amd64, arm64, arm) — belum ada Windows
|
||||
- Belum ada trigger untuk branch `dev` (demo build)
|
||||
- Pakai GitHub Actions syntax (`softprops/action-gh-release`) yang mungkin tidak kompatibel dengan Gitea Actions
|
||||
- Release job pakai `github.ref` prefix yang perlu disesuaikan
|
||||
|
||||
Gitea server: `ssh@172.20.8.92`
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] 1. Update `connect_remote.txt` — tambahkan Gitea build server info
|
||||
- [x] 2. Rewrite `.gitea/workflows/build.yml` — trigger dev=demo, main=production, build linux+windows
|
||||
- [x] 3. Verify workflow syntax — pasti Gitea Actions compatible
|
||||
- [x] 4. Push ke Gitea repo dan test CI pipeline
|
||||
- [x] 5. Cek build artifacts di Gitea Actions dashboard
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
- [x] F1. Workflow trigger: push ke `dev` → demo build run, push ke `main` → production build run
|
||||
- [x] F2. Build matrix: `linux/amd64`, `linux/arm64`, `windows/amd64` — semua artifact ter-generate
|
||||
- [x] F3. Artifact naming: demo=`nexus-device-agent-demo-*`, production=`nexus-device-agent-*`
|
||||
- [x] F4. Gitea Actions dashboard menunjukkan workflow successfully completed
|
||||
@@ -0,0 +1,414 @@
|
||||
# Real-Time Traffic Monitoring (Optimized)
|
||||
|
||||
## TL;DR
|
||||
> Real-time device/node status via SSE + HTTP streaming, traffic monitoring with PostgreSQL, historical charts with daily aggregation, toggle controls. **Optimized for low resource usage** — SSE only active when tab is focused, charts lazy-loaded.
|
||||
|
||||
**Deliverables**:
|
||||
- HTTP streaming for device-agent → server (Rx/Tx data)
|
||||
- SSE endpoint for dashboard real-time updates
|
||||
- PostgreSQL schema for traffic logging
|
||||
- Traffic recorder (Redis → DB batch)
|
||||
- Dashboard traffic chart with historical data (lazy-loaded)
|
||||
- Toggle to disable real-time display (per device/global)
|
||||
- **Tab visibility API** — SSE disconnects when tab inactive
|
||||
|
||||
**Estimated Effort**: Medium
|
||||
**Parallel Execution**: YES - 3 waves
|
||||
**Critical Path**: T1 → T2 → T3 → T4 → T5
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request
|
||||
User wants real-time device/node online status without page refresh, Rx/Tx traffic with charts, daily/historical logging, and toggle controls. System scales to 1000+ devices.
|
||||
|
||||
### Architecture Decision (Updated)
|
||||
- **Device-Agent → Server**: HTTP POST streaming (no protoc needed, uses existing HTTP)
|
||||
- **Dashboard ← Server**: SSE (browser native, auto-reconnect, **tab-aware**)
|
||||
- **Real-time state**: Redis (fast in-memory, pub/sub)
|
||||
- **Traffic recording**: PostgreSQL (plain, TimescaleDB can be added later)
|
||||
- **Historical query**: PostgreSQL with time_bucket aggregation
|
||||
|
||||
### Optimization Strategy
|
||||
1. **Tab Visibility API** — SSE disconnects when browser tab is inactive
|
||||
2. **Lazy-load charts** — TrafficChart only mounts when user clicks "Show Chart"
|
||||
3. **Polling interval** — SSE pushes every 5s, not every 1s
|
||||
4. **Redis TTL** — Traffic data expires after 24h (batch sync to DB)
|
||||
5. **Minimal DOM updates** — Chart only re-renders on data change
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
Real-time device status + traffic monitoring for 1000+ devices with historical charts, optimized for low resource usage.
|
||||
|
||||
### Must Have
|
||||
- HTTP streaming for agent traffic data
|
||||
- SSE for dashboard real-time updates
|
||||
- **Tab-aware SSE** (disconnect when tab inactive)
|
||||
- PostgreSQL for traffic logging
|
||||
- Traffic chart per device/node (lazy-loaded)
|
||||
- Toggle to disable chart display
|
||||
- Historical data query (daily/hourly)
|
||||
|
||||
### Must NOT Have
|
||||
- Do NOT use gRPC (no protoc dependency)
|
||||
- Do NOT add heavy chart libraries (use lightweight SVG)
|
||||
- Do NOT keep SSE connections open when tab is inactive
|
||||
- Do NOT render charts when not visible
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
### Test Decision
|
||||
- **Infrastructure exists**: YES (Go, Vue 3, PostgreSQL)
|
||||
- **Automated tests**: Tests-after
|
||||
- **Framework**: Go test + npm test
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
### Parallel Execution Waves
|
||||
|
||||
```
|
||||
Wave 1 (Foundation):
|
||||
├── T1: PostgreSQL schema + migration
|
||||
├── T2: HTTP traffic endpoint
|
||||
└── T3: Traffic recorder (Redis → DB)
|
||||
|
||||
Wave 2 (Backend + Frontend):
|
||||
├── T4: SSE endpoint (tab-aware)
|
||||
├── T5: Dashboard traffic chart (lazy-loaded)
|
||||
├── T6: Toggle controls
|
||||
└── T7: Historical data view
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] 1. **PostgreSQL schema + migration**
|
||||
|
||||
**What to do**:
|
||||
- Create migration file `apps/server-core/migrations/003_device_traffic.sql`
|
||||
- Create `device_traffic` table:
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS device_traffic (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
device_id UUID NOT NULL,
|
||||
node_id UUID,
|
||||
rx_bytes BIGINT DEFAULT 0,
|
||||
tx_bytes BIGINT DEFAULT 0,
|
||||
rx_rate BIGINT DEFAULT 0,
|
||||
tx_rate BIGINT DEFAULT 0
|
||||
);
|
||||
```
|
||||
- Create daily aggregate view
|
||||
- Create hourly aggregate view
|
||||
- Add indexes on device_id + time
|
||||
|
||||
**Must NOT do**:
|
||||
- Do NOT use TimescaleDB extension (not installed)
|
||||
- Do NOT remove existing tables
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: `[]`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with T2, T3)
|
||||
- **Parallel Group**: Wave 1
|
||||
- **Blocks**: T4
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `apps/server-core/migrations/` - existing migration pattern
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] `go build -tags dev ./...` passes
|
||||
- [x] Migration file created with correct SQL
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(db): add device_traffic table and views`
|
||||
- Files: `apps/server-core/migrations/003_device_traffic.sql`
|
||||
|
||||
- [x] 2. **HTTP traffic endpoint**
|
||||
|
||||
**What to do**:
|
||||
- Create `apps/server-core/api/traffic_stream.go`:
|
||||
- `POST /api/v1/traffic/report` — receive traffic data from agent
|
||||
- `GET /api/v1/traffic/stream` — SSE for dashboard
|
||||
- Traffic report endpoint accepts JSON: `{device_id, rx_bytes, tx_bytes}`
|
||||
- Stores to Redis via TrafficRecorder
|
||||
- No protoc needed — pure HTTP
|
||||
|
||||
**Must NOT do**:
|
||||
- Do NOT require authentication for traffic reports (agent → server)
|
||||
- Do NOT block on Redis write
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: `[]`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with T1, T3)
|
||||
- **Parallel Group**: Wave 1
|
||||
- **Blocks**: T4
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `apps/server-core/api/heartbeat.go` - existing HTTP pattern
|
||||
- `apps/server-core/internal/traffic/recorder.go` - TrafficRecorder
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] `go build -tags dev ./...` passes
|
||||
- [x] POST /api/v1/traffic/report accepts traffic data
|
||||
- [x] Data stored to Redis
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(api): add HTTP traffic report endpoint`
|
||||
- Files: `apps/server-core/api/traffic_stream.go`
|
||||
|
||||
- [x] 3. **Traffic recorder (Redis → DB batch)**
|
||||
|
||||
**What to do**:
|
||||
- Create `apps/server-core/internal/traffic/recorder.go`:
|
||||
- `TrafficRecorder` struct with Redis client + DB connection
|
||||
- `Record(deviceID, rxBytes, txBytes)` — fast Redis write
|
||||
- `StartBatchSync(ctx, interval)` — batch insert to DB every 60s
|
||||
- `GetDeviceTraffic(deviceID, from, to)` — query historical data
|
||||
- `GetNodeTraffic(nodeID, from, to)` — aggregate per node
|
||||
- Redis key: `traffic:{device_id}:{timestamp}`
|
||||
- Batch insert: collect from Redis, insert to DB, delete from Redis
|
||||
|
||||
**Must NOT do**:
|
||||
- Do NOT block on Redis write
|
||||
- Do NOT query DB on every traffic report
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: `[]`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with T1, T2)
|
||||
- **Parallel Group**: Wave 1
|
||||
- **Blocks**: T4
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `apps/server-core/internal/heartbeat/redis.go` - Redis pattern
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] `go build -tags dev ./...` passes
|
||||
- [x] Traffic recorded to Redis on Report()
|
||||
- [x] Batch sync inserts to DB
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(traffic): add Redis → PostgreSQL recorder`
|
||||
- Files: `apps/server-core/internal/traffic/recorder.go`
|
||||
|
||||
- [x] 4. **SSE endpoint (tab-aware)**
|
||||
|
||||
**What to do**:
|
||||
- Create `apps/server-core/api/sse.go`:
|
||||
- `SSEHandler` struct with Redis + recorder
|
||||
- `StreamStatus(c *gin.Context)` — SSE endpoint
|
||||
- Pushes device status updates every 5s
|
||||
- Heartbeat ping every 30s (keep-alive)
|
||||
- Register route: `GET /api/v1/devices/stream`
|
||||
- **Frontend optimization**: Use Page Visibility API
|
||||
- `document.addEventListener('visibilitychange', ...)`
|
||||
- When tab hidden → disconnect SSE
|
||||
- When tab visible → reconnect SSE
|
||||
|
||||
**Must NOT do**:
|
||||
- Do NOT keep SSE open when tab is inactive
|
||||
- Do NOT store SSE clients in memory
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- **Skills**: `[]`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO (depends on T1, T2, T3)
|
||||
- **Parallel Group**: Wave 2
|
||||
- **Blocks**: T5
|
||||
- **Blocked By**: T1, T2, T3
|
||||
|
||||
**References**:
|
||||
- `apps/server-core/api/heartbeat.go` - existing pattern
|
||||
- SSE spec: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] `go build -tags dev ./...` passes
|
||||
- [x] `curl -N http://localhost:8080/api/v1/devices/stream` returns SSE stream
|
||||
- [x] SSE disconnects when tab inactive (frontend)
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(sse): add device status streaming endpoint`
|
||||
- Files: `apps/server-core/api/sse.go`, `apps/server-core/main.go`
|
||||
|
||||
- [x] 5. **Dashboard traffic chart (lazy-loaded)**
|
||||
|
||||
**What to do**:
|
||||
- Create `apps/dashboard-ui/src/components/TrafficChart.vue`:
|
||||
- SVG line chart (no heavy libraries)
|
||||
- Props: `deviceId`, `height`, `showToggle`
|
||||
- **Lazy-load**: Only render when `showChart` prop is true
|
||||
- Time range selector (1h, 6h, 24h, 7d, 30d)
|
||||
- Toggle to enable/disable real-time updates
|
||||
- Add chart to `DeviceDetail.vue` (per-device, behind toggle)
|
||||
- Add chart to `Dashboard.vue` (per-node aggregate)
|
||||
|
||||
**Must NOT do**:
|
||||
- Do NOT add heavy chart libraries (use SVG)
|
||||
- Do NOT render chart when `showChart` is false
|
||||
- Do NOT block UI on chart render
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
- **Skills**: `[]`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with T6, T7)
|
||||
- **Parallel Group**: Wave 2
|
||||
- **Blocks**: None
|
||||
- **Blocked By**: T4
|
||||
|
||||
**References**:
|
||||
- `apps/dashboard-ui/src/views/DeviceDetail.vue` - existing page
|
||||
- SVG chart pattern
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] `npm run build` passes
|
||||
- [x] Chart only renders when toggle is ON
|
||||
- [x] Time range selector works
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(ui): add lazy-loaded traffic chart component`
|
||||
- Files: `apps/dashboard-ui/src/components/TrafficChart.vue`
|
||||
|
||||
- [x] 6. **Toggle controls**
|
||||
|
||||
**What to do**:
|
||||
- Add toggle to `DeviceDetail.vue`:
|
||||
- "Show Traffic Chart" toggle (per device)
|
||||
- When OFF: chart hidden, no data fetched
|
||||
- When ON: chart visible, data fetched
|
||||
- Add global toggle to `Dashboard.vue`:
|
||||
- "Show All Charts" toggle
|
||||
- Saves preference to localStorage
|
||||
- **Tab visibility**: Implement Page Visibility API
|
||||
- `document.addEventListener('visibilitychange', handler)`
|
||||
- When tab hidden → disconnect SSE, stop polling
|
||||
- When tab visible → reconnect SSE, resume polling
|
||||
|
||||
**Must NOT do**:
|
||||
- Do NOT render charts when toggle is OFF
|
||||
- Do NOT fetch data when chart is hidden
|
||||
- Do NOT keep SSE open when tab is inactive
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
- **Skills**: `[]`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with T5, T7)
|
||||
- **Parallel Group**: Wave 2
|
||||
- **Blocks**: None
|
||||
- **Blocked By**: T5
|
||||
|
||||
**References**:
|
||||
- Page Visibility API: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
|
||||
- localStorage pattern
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] `npm run build` passes
|
||||
- [x] Per-device toggle works
|
||||
- [x] Global toggle works
|
||||
- [x] SSE disconnects when tab hidden
|
||||
- [x] Charts hidden when toggle OFF
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(ui): add toggle controls + tab-aware SSE`
|
||||
- Files: `apps/dashboard-ui/src/views/DeviceDetail.vue`, `apps/dashboard-ui/src/views/Dashboard.vue`
|
||||
|
||||
- [x] 7. **Historical data view**
|
||||
|
||||
**What to do**:
|
||||
- Create `apps/dashboard-ui/src/views/TrafficHistory.vue`:
|
||||
- Full-page traffic history view
|
||||
- Date range picker
|
||||
- Device/node selector
|
||||
- Export to CSV
|
||||
- Daily/hourly aggregation
|
||||
- Add route: `/traffic-history`
|
||||
- Query backend traffic API
|
||||
- **Lazy-load**: Only fetch data when view is active
|
||||
|
||||
**Must NOT do**:
|
||||
- Do NOT fetch data on page load (wait for user action)
|
||||
- Do NOT expose raw data
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
- **Skills**: `[]`
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with T5, T6)
|
||||
- **Parallel Group**: Wave 2
|
||||
- **Blocks**: None
|
||||
- **Blocked By**: T4
|
||||
|
||||
**References**:
|
||||
- `apps/dashboard-ui/src/router/index.ts` - routing
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [x] `npm run build` passes
|
||||
- [x] History page accessible at /traffic-history
|
||||
- [x] Date range filter works
|
||||
- [x] Data only fetched on user action
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(ui): add traffic history view`
|
||||
- Files: `apps/dashboard-ui/src/views/TrafficHistory.vue`, `apps/dashboard-ui/src/router/index.ts`
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
- [x] F1. **Plan Compliance Audit** — `oracle`
|
||||
- [x] F2. **Code Quality Review** — `unspecified-high`
|
||||
- [x] F3. **Real Manual QA** — `unspecified-high`
|
||||
- [x] F4. **Scope Fidelity Check** — `deep`
|
||||
|
||||
---
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
- Commit #1: Backend — PostgreSQL schema + HTTP endpoint + recorder
|
||||
- Commit #2: Frontend — SSE + charts + toggles + history
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Verification Commands
|
||||
```bash
|
||||
go build -tags dev ./... # Expected: no errors
|
||||
cd apps/dashboard-ui && npm run build # Expected: no errors
|
||||
```
|
||||
|
||||
### Final Checklist
|
||||
- [x] HTTP traffic endpoint works (no protoc needed)
|
||||
- [x] SSE pushes real-time status to dashboard
|
||||
- [x] **SSE disconnects when tab inactive**
|
||||
- [x] **Charts lazy-loaded (only when toggle ON)**
|
||||
- [x] PostgreSQL stores traffic data
|
||||
- [x] Toggle controls work (per device + global)
|
||||
- [x] Performance: minimal resource usage
|
||||
Reference in New Issue
Block a user