Implementation Plan 001: Environment Inspection Web Dashboard¶
Status¶
Draft — Revised (v2)
Revision History¶
| Version | Date | Summary |
|---|---|---|
| v1 | — | Initial plan: 6 phases, 42 tasks, basic dashboard |
| v2 | 2026-04-01 | Restructured: 8 phases, expanded scope — shared collector module, redaction layer, REST API, tiered output, derived insights, static HTML export, diff-against-previous-scan |
Summary¶
Build the local environment inspection web dashboard and REST API described in Blueprint 001 and approved in ADR 041.
The v2 plan reflects the expanded scope: extract data collection into a
shared plugin-based module (scripts/_env_collectors/), implement a
redaction layer for secret stripping, build both HTML and JSON endpoints,
support tiered output, compute derived insights, and provide static HTML
export with strict security controls.
Origin¶
Prerequisites¶
- [x] Exploration concluded — Proceed
- [x] Blueprint accepted (v2)
- [x] ADR accepted
- [ ]
fastapi[standard]available on PyPI (it is — just needs adding)
Task Order¶
Phase 1: Shared Collector Module and Redaction Layer¶
Extract gather_env_info() into a shared, plugin-based module. This is
the foundation — both the CLI and dashboard depend on it. The redaction
layer must be in place before any data reaches a UI or export.
| # | Task | Files changed | Notes |
|---|---|---|---|
| 1 | Create scripts/_env_collectors/ package |
scripts/_env_collectors/__init__.py |
gather_env_info(tier, redact_level), collector registry, schema version |
| 2 | Create _base.py — BaseCollector ABC |
scripts/_env_collectors/_base.py |
name, tier, timeout, collect() method, timeout wrapper, error isolation |
| 3 | Create _redact.py — redaction layer |
scripts/_env_collectors/_redact.py |
RedactLevel enum, pattern-based redaction rules, env var allowlist, URL credential stripping, high-entropy detection |
| 4 | Create system.py collector |
scripts/_env_collectors/system.py |
OS, arch, hostname, shell, cwd, privilege, locale (Minimal tier) |
| 5 | Create runtimes.py collector |
scripts/_env_collectors/runtimes.py |
Python versions/installations, compilers, version managers (Minimal tier) |
| 6 | Create path_analysis.py collector |
scripts/_env_collectors/path_analysis.py |
PATH dirs, duplicates, dead entries, ordering, exec counts (Minimal tier) |
| 7 | Create project.py collector |
scripts/_env_collectors/project.py |
Repo root, lockfiles, build/test/lint tools, config files (Minimal tier) |
| 8 | Create git_info.py collector |
scripts/_env_collectors/git_info.py |
Git version, branch, dirty state, remote URLs (Minimal tier) |
| 9 | Create venv.py collector |
scripts/_env_collectors/venv.py |
Virtualenv detection, Hatch envs, mismatch (Standard tier) |
| 10 | Create packages.py collector |
scripts/_env_collectors/packages.py |
Installed packages, outdated, duplicates, entry points (Full tier) |
| 11 | Create network.py collector |
scripts/_env_collectors/network.py |
DNS, proxy, outbound summary (Standard tier) |
| 12 | Create filesystem.py collector |
scripts/_env_collectors/filesystem.py |
Writable dirs, disk space, mounts (Standard tier) |
| 13 | Create security.py collector |
scripts/_env_collectors/security.py |
Secret scan, permissions, insecure PATH (Standard tier) |
| 14 | Create container.py collector |
scripts/_env_collectors/container.py |
Docker/CI/WSL/cloud detection, resource limits (Standard tier) |
| 15 | Create insights.py collector |
scripts/_env_collectors/insights.py |
Derived warnings from cross-section analysis (Standard tier) |
| 16 | Update env_inspect.py to import from _env_collectors |
scripts/env_inspect.py |
Replace monolithic gather_env_info() with import from shared module. CLI output and flags unchanged. |
| 17 | Verify: python scripts/env_inspect.py works identically |
— | Regression test: output should match pre-refactor |
| 18 | Verify: python scripts/env_inspect.py --json output passes redaction |
— | JSON output should redact secrets at SECRETS level by default |
Phase 2: Dashboard Scaffolding and Backend¶
Set up the directory, dependencies, and a minimal working server.
| # | Task | Files changed | Notes |
|---|---|---|---|
| 19 | Create tools/dev-tools/env-dashboard/ directory structure |
tools/dev-tools/env-dashboard/__init__.py |
Empty __init__.py |
| 20 | Add dashboard optional-dependency group |
pyproject.toml |
fastapi[standard] under [project.optional-dependencies] |
| 21 | Add Hatch dashboard environment |
pyproject.toml |
[tool.hatch.envs.dashboard] with features = ["dashboard"] and serve script |
| 22 | Add Taskfile shortcut | Taskfile.yml |
dashboard:serve task |
| 23 | Create app.py — FastAPI app factory |
tools/dev-tools/env-dashboard/app.py |
Mount static files, configure Jinja2 (autoescape=True), if __name__ starts Uvicorn on 127.0.0.1:8000 |
| 24 | Create collector.py — caching adapter |
tools/dev-tools/env-dashboard/collector.py |
Import from _env_collectors, cache results with TTL, expose get_report(tier, redact) |
| 25 | Create redact.py — dashboard redaction wiring |
tools/dev-tools/env-dashboard/redact.py |
Thin wrapper connecting _redact.py to route-level ?redact= parameter |
| 26 | Create routes.py — HTML route handlers |
tools/dev-tools/env-dashboard/routes.py |
GET / (index), GET /section/{name} (htmx partial) |
| 27 | Create api.py — JSON API route handlers |
tools/dev-tools/env-dashboard/api.py |
GET /api/summary, GET /api/report, GET /api/warnings, GET /api/sections/:name, POST /api/scan, GET /api/export.json |
| 28 | Verify: hatch run dashboard:serve starts and returns HTTP 200 |
— | Manual smoke test |
Phase 3: Templates and Static Assets¶
Build the HTML shell, vendor JS/CSS, and create the index page.
| # | Task | Files changed | Notes |
|---|---|---|---|
| 29 | Vendor Pico CSS into static/css/pico.min.css |
tools/.../static/css/pico.min.css |
Download from picocss.com (MIT licence) |
| 30 | Create style.css — custom properties and layout |
tools/.../static/css/style.css |
--color-pass/warn/fail tokens, dashboard grid, section card styles, badge styles |
| 31 | Vendor htmx into static/js/htmx.min.js |
tools/.../static/js/htmx.min.js |
Download from htmx.org (BSD licence) |
| 32 | Vendor Alpine.js into static/js/alpine.min.js |
tools/.../static/js/alpine.min.js |
Download from alpinejs.dev (MIT licence) |
| 33 | Create base.html — layout shell |
tools/.../templates/base.html |
Nav sidebar, top summary bar, controls bar (search, redact toggle, hide empty, export, refresh), footer, <script> tags, {% block content %} |
| 34 | Create index.html — dashboard home |
tools/.../templates/index.html |
Summary bar + warnings panel + grid of section cards with hx-get triggers and pass/warn/fail badges |
| 35 | Create favicon.svg |
tools/.../static/img/favicon.svg |
Simple Python-themed SVG |
| 36 | Verify: dashboard loads in browser with styled layout | — | Nav, cards, Pico styles, controls render correctly |
Phase 4: Section Partials (Core Sections)¶
Build templates for the highest-value sections first.
| # | Task | Files changed | Notes |
|---|---|---|---|
| 37 | partials/summary.html — top summary bar |
tools/.../templates/partials/summary.html |
OS, hostname, user, shell, Python, git, container/CI, warnings count, timestamp |
| 38 | partials/warnings.html — warnings panel |
tools/.../templates/partials/warnings.html |
List of derived insights with pass/warn/fail badges |
| 39 | partials/system.html — system info |
tools/.../templates/partials/system.html |
OS, kernel, arch, shell, locale, privilege |
| 40 | partials/runtimes.html — runtimes |
tools/.../templates/partials/runtimes.html |
Python versions, installations, compilers |
| 41 | partials/path.html — PATH analysis |
tools/.../templates/partials/path.html |
Entries, duplicates, dead entries, ordering |
| 42 | partials/git.html — Git info |
tools/.../templates/partials/git.html |
Version, branch, dirty state, remotes (redacted) |
| 43 | partials/venv.html — virtualenvs |
tools/.../templates/partials/venv.html |
Active venv, Hatch envs, mismatch |
| 44 | Verify: seven core sections load via htmx and display data | — | Manual smoke test |
Phase 5: Remaining Sections¶
| # | Task | Files changed | Notes |
|---|---|---|---|
| 45 | partials/project.html — project info |
tools/.../templates/partials/project.html |
Repo root, lockfiles, build tools, configs |
| 46 | partials/packages.html — packages |
tools/.../templates/partials/packages.html |
Grouped by location, Alpine.js search/filter, expandable groups |
| 47 | partials/network.html — network |
tools/.../templates/partials/network.html |
DNS, proxy, outbound, interfaces |
| 48 | partials/filesystem.html — filesystem |
tools/.../templates/partials/filesystem.html |
Writable dirs, disk space, mounts |
| 49 | partials/security.html — security |
tools/.../templates/partials/security.html |
Secret scan results, permission warnings |
| 50 | partials/container.html — container/CI |
tools/.../templates/partials/container.html |
Docker/CI/WSL/cloud, resource limits |
| 51 | partials/raw_json.html — raw JSON viewer |
tools/.../templates/partials/raw_json.html |
Formatted, syntax-highlighted full scan JSON |
| 52 | Verify: all sections render correctly | — | Full manual walkthrough |
Phase 6: Interactive Features and Controls¶
| # | Task | Files changed | Notes |
|---|---|---|---|
| 53 | Implement search box | base.html, index.html |
Alpine.js x-model filtering across all visible sections |
| 54 | Implement hide/show empty fields toggle | base.html, style.css |
Alpine.js toggle, CSS classes to hide empty rows |
| 55 | Implement redact secrets toggle | base.html, routes.py |
Toggle switches between NONE and SECRETS via htmx re-render |
| 56 | Implement copy field value | partials/*.html |
Clipboard API button on each field value |
| 57 | Implement refresh scan | base.html, routes.py, api.py |
Button triggers POST /api/scan then refreshes all sections via htmx |
| 58 | Implement timestamps | partials/summary.html, collector.py |
Scan timestamp in summary, per-section timestamps |
| 59 | Lazy-load slow sections | routes.py, partials/packages.html |
hx-trigger="load" for outdated check, spinner while loading |
| 60 | Per-section refresh buttons | partials/*.html |
hx-get + hx-target on a refresh icon per section |
| 61 | Dark/light mode toggle | style.css, base.html |
CSS prefers-color-scheme + Alpine.js toggle switch |
| 62 | Verify: all interactive features work | — | Manual smoke test |
Phase 7: Export and Diff¶
| # | Task | Files changed | Notes |
|---|---|---|---|
| 63 | Create export.py — static HTML export logic |
tools/.../export.py |
Renders export.html template, inlines CSS, strips JS, adds CSP meta tag |
| 64 | Create export.html — self-contained export template |
tools/.../templates/export.html |
No JS, inline CSS, redaction level + timestamp header, warning banner if low redaction |
| 65 | Add GET /api/export.html endpoint |
api.py |
Returns static HTML with Content-Disposition: attachment |
| 66 | Add export dropdown to UI | base.html |
Export JSON / Export HTML buttons |
| 67 | Implement diff against previous scan | collector.py, routes.py, partials/ |
Cache previous scan result, compute delta, highlight changes in UI |
| 68 | Verify: export generates valid HTML with correct redaction | — | Download export, verify no secrets, verify CSP, verify no JS |
| 69 | Verify: diff highlights changes correctly | — | Run scan, change something, re-scan, check diff |
Phase 8: Documentation and Cleanup¶
| # | Task | Files changed | Notes |
|---|---|---|---|
| 70 | Create tools/dev-tools/env-dashboard/README.md |
tools/.../README.md |
Usage, development, architecture, security constraints |
| 71 | Create scripts/_env_collectors/README.md |
scripts/_env_collectors/README.md |
Module purpose, how to add collectors, tier reference |
| 72 | Extend Prettier to cover *.css files |
.pre-commit-config.yaml |
Add CSS to Prettier file patterns |
| 73 | Update docs/design/tool-decisions.md |
docs/design/tool-decisions.md |
Note FastAPI, htmx, Alpine.js choices |
| 74 | Update docs/repo-layout.md |
docs/repo-layout.md |
Add tools/dev-tools/ and scripts/_env_collectors/ |
| 75 | Update .github/copilot-instructions.md |
.github/copilot-instructions.md |
Add ADR 041 to key ADRs if warranted |
| 76 | Update scripts/README.md |
scripts/README.md |
Document _env_collectors shared module |
| 77 | Final verification: full walkthrough | — | All sections, API, search, refresh, export, diff, dark mode |
Migrations / Refactors¶
gather_env_info() extraction¶
The primary refactor is extracting data-collection logic from
env_inspect.py into scripts/_env_collectors/. This is the only
change that touches existing code.
Migration strategy:
- Create the
_env_collectorspackage with all collector files. - Move logic from
env_inspect.pyprivate functions into collectors. - Update
gather_env_info()inenv_inspect.pyto delegate to_env_collectors.gather_env_info(). - Verify CLI output is identical (regression test).
- Deprecate the old private functions in
env_inspect.py.
Risk: If existing code imports env_inspect.gather_env_info(), it
continues to work — the function still exists, it just delegates. No
external breakage.
Testing Checklist¶
Shared collector module¶
- [ ]
gather_env_info()returns valid data at each tier - [ ] Each collector handles timeout gracefully
- [ ] Each collector handles errors without crashing the scan
- [ ] Redaction strips known secret patterns
- [ ] Redaction preserves allowlisted env var values
- [ ] URL credential stripping works (scheme://user:pass@host)
- [ ] High-entropy detection at PARANOID level works
- [ ] Schema version is present in output
- [ ]
env_inspect.pyCLI output unchanged after refactor
Dashboard¶
- [ ]
hatch run dashboard:servestarts without errors - [ ]
http://127.0.0.1:8000returns HTTP 200 - [ ] Index page renders with summary bar, warnings panel, section cards
- [ ] Each section loads correctly via htmx
- [ ] Pass/warn/fail badges display correctly
- [ ] Search box filters across sections (Alpine.js)
- [ ] Hide/show empty fields works
- [ ] Redact toggle switches between NONE and SECRETS
- [ ] Copy field value copies to clipboard
- [ ] Raw JSON viewer shows formatted output
- [ ] Lazy-load spinner shows for slow sections
- [ ] Per-section refresh updates data
- [ ] Refresh scan re-runs all collectors
- [ ] Timestamps display on summary and per-section
- [ ] Dark/light mode toggle works
- [ ] Diff against previous scan highlights changes
API¶
- [ ]
GET /api/summaryreturns summary JSON - [ ]
GET /api/reportreturns full scan JSON - [ ]
GET /api/warningsreturns warnings list - [ ]
GET /api/sections/:namereturns single section - [ ]
POST /api/scantriggers fresh scan - [ ]
GET /api/export.jsonreturns downloadable JSON with PII redaction - [ ]
?redact=parameter works on all endpoints
Export¶
- [ ] Static HTML export contains no JavaScript
- [ ] Export has CSP meta tag blocking scripts
- [ ] Export uses PII-level redaction by default
- [ ] Export includes timestamp and redaction level header
- [ ] Warning banner appears when redaction < PII
- [ ]
Content-Disposition: attachmentheader is set - [ ] HTML is valid and renders correctly when opened in browser
- [ ] No external URLs in export
Security¶
- [ ] Jinja2 autoescape active (test with env var containing
<script>) - [ ] Server binds to
127.0.0.1only (verify withnetstat/ss) - [ ] Redaction active by default (SECRETS level)
- [ ] Export redaction defaults to PII level
- [ ] No raw secret values in HTML source at SECRETS level
- [ ]
task dashboard:serveworks as Taskfile shortcut - [ ] No console errors in browser DevTools
Rollout Notes¶
- Feature flags: None — the dashboard is opt-in via
hatch run dashboard:serve. It doesn't affect any existing workflow. - Breaking changes:
env_inspect.pyinternal functions are moved to_env_collectorsbut the publicgather_env_info()interface is preserved. The CLI works identically. - Rollback plan: Revert
env_inspect.pychanges (restore inline functions), deletescripts/_env_collectors/andtools/dev-tools/env-dashboard/, remove thedashboarddependency group and Hatch env frompyproject.toml, remove the Taskfile task. - Documentation updates: READMEs in
_env_collectors/andenv-dashboard/, plus updates torepo-layout.md,tool-decisions.md, andscripts/README.md.
Risks & Mitigations¶
| Risk | Impact | Mitigation |
|---|---|---|
| Scope creep — dashboard grows beyond documented features | Med | Freeze feature list at v2 design. Defer unlisted features to future blueprints. |
| Secret leakage in exports | High | Redaction layer is Phase 1. Exports default to PII level. No JavaScript in exports. CSP meta tag. |
gather_env_info() refactor breaks CLI |
Med | Regression test: compare CLI output before/after refactor. Keep public interface unchanged. |
| New collectors are slow | Med | Per-collector timeout + htmx lazy-loading. Deep scan is opt-in. |
| Vendored JS becomes outdated | Low | Pin versions in README. Update periodically. No security risk for local-only tool. |
| Template users don't want the dashboard | Low | Self-contained in tools/dev-tools/env-dashboard/. Delete the directory — no impact. |
| Insights rules produce false positives | Med | Each insight has a severity level. Keep rules conservative — warn only when confident. |