cnHeat-2.0-style automation hooks. Coverage prediction is now a
first-class API resource that Terraform / Ansible / cron can drive.
REST API (under /api/v1, Bearer-token auth scoped to one org)
- GET /coverages list
- POST /coverages create + auto-queue compute
- GET /coverages/:id read (includes status / progress_pct
for polling-based providers)
- PATCH /coverages/:id update editable fields
- DELETE /coverages/:id delete + cleanup
- POST /coverages/:id/recompute requeue, returns 202
- GET /coverages/:id/kmz Google Earth bundle (KML + PNG)
Each response includes the full resource — Terraform read-after-write
reconciles cleanly; status polling hits a stable JSON shape.
Email-on-complete (Towerops.Coverages.Notifier)
- Worker fires deliver_compute_complete on both :ready and :failed
paths. Sends a short text email to every member of the owning
organisation with a link to the show page (or the failure
reason). Mirrors cnHeat 2.0's per-project email alerts.
- Failures are non-fatal: a stuck SMTP relay never blocks the
underlying coverage record from transitioning state.
KMZ export
- Builds a flat KMZ in-memory via :zip.create — doc.kml +
overlay.png, with the LatLonBox set from the coverage's bbox.
XML special chars are escaped. 409 if not yet ready.
Tests (11 new, all green)
- Bearer-token auth happy path + 401 missing
- index / show / create / update / delete + 404 cross-org
- create returns status:'queued' confirming the auto-enqueue
- recompute returns 202 with status:'queued'
Plus docs/terraform-coverages.md showing how to drive the new API
from a third-party restapi provider until the first-party
terraform-provider-towerops gets a coverage resource.
Promoted pure presentation and utility helpers from `defp` to `def @doc false`
across ~20 LiveViews, Oban workers, and sync modules so they're reachable from
unit tests. Refactored several `cond` blocks into idiomatic function heads with
guards. Added ~250 new test cases in new files under test/towerops and
test/towerops_web, including DB-backed tests for CnMaestro.Sync and
AlertNotificationWorker, and removed dead LiveView tab components and
CapacityLive (no callers anywhere in lib/test).
Configured mix.exs test_coverage.ignore_modules to exclude vendored third-party
code (SnmpKit, protobuf-generated Towerops.Agent.*, Absinthe GraphQL types,
Phoenix HTML modules, Inspect protocol impls) from coverage calculations —
these are not our project code.
Coverage: 66.93% → 70.09%. Full suite: 10,127 tests, 0 failures.
Intercepts requests with Accept: text/markdown at endpoint level (before
the router's :accepts plug rejects them) and returns hardcoded markdown
representations for /, /privacy, and /terms.
## Summary
- Every device now automatically gets an ICMP ping check (`source_type: "auto_discovery"`) when created via `Monitoring.ensure_default_ping_check/1`
- When a device IP changes, the ping check host config is synced automatically
- Backfill migration inserts ICMP Ping checks for all existing devices that don't have one
- Fixes checkbox alignment in the Add Service Check modal (verify SSL / follow redirects)
## Test plan
- [x] 4 new tests for `ensure_default_ping_check/1` (create, idempotent, IP update, no-op)
- [x] 3 new tests for device lifecycle (auto-create on device create, sync on IP change, no-op when IP unchanged)
- [x] 13 existing tests updated to account for auto-created ping check
- [x] Full test suite passes (`mix precommit`)
- [ ] Verify backfill migration on staging
- [ ] Create device in UI, confirm ping check appears in service checks list
- [ ] Verify checkbox alignment in Add Service Check modal
Reviewed-on: graham/towerops-web#68
- REST CRUD at /api/v1/checks for HTTP, TCP, DNS, ping checks
- GraphQL queries (checks, check) and mutations (create/update/delete)
- ping executor using system ping with macOS/Linux parsing
- check config validation for ping type (requires host)
- API documentation updated with checks resource section
Reviewed-on: graham/towerops-web#52
the complete_qr_login endpoint was returning session.token (the SHA256
hash stored in the database) instead of session.raw_token (the plaintext).
this caused all subsequent API calls and WebSocket connections to fail
with 401/REFUSED since the server would hash the already-hashed token.
adds a regression test that verifies the returned token is usable for
authenticated API requests.
Reviewed-on: graham/towerops-web#42
Tests for agents, alerts, integrations, organization, members, and
invitations controllers. Refactor alert filters to reduce complexity.
75 new tests, all passing.
REST endpoints for CRUD, scoped filtering (active/upcoming/past).
GraphQL queries and mutations for maintenance windows.
Fix agent edit test assertion message.
REST API v1 endpoints for schedules (14 endpoints) and escalation
policies (10 endpoints) with full CRUD for nested resources (layers,
members, overrides, rules, targets). GraphQL types, resolvers, and
schema mutations for both resource trees.
The test makes a real HTTP call to GitHub which can fail or return
different responses depending on network conditions, rate limits, etc.
Tagged as :integration to exclude from normal test runs, consistent
with other GitHub API tests in ReleaseCheckerTest.
- Remove all 25ms and 50ms sleeps from agent_channel_test
- Add poll_until helper for async DB operations (early exit on success)
- Reduce timeout test sleeps from 100ms to 60ms
- Reduce polling delays from 10ms to 5ms in multiple tests
- Remove unnecessary timestamp and background task sleeps
- Reduce circuit breaker recovery sleep from 150ms to 110ms
Performance: 30.3s → 28.3s (6.6% faster)
Total improvement from baseline: 52s → 28.3s (45.6% faster)
Two critical production bugs fixed:
1. **Alert type enum → string conversion**
- Changed Alert.alert_type from Ecto.Enum to :string for flexibility
- Updated all queries to use "device_down"/"device_up" strings instead of atoms
- Fixed pattern matching in alerts.ex and device_monitor_worker.ex
- Updated 44+ test files to use string literals
2. **SQL array indexing syntax error in activity feed**
- Fixed PostgreSQL syntax: `array_agg(...)[1]` → `(array_agg(...))[1]`
- Prevents "syntax error at or near [" in activity feed queries
3. **Added comprehensive timer cleanup tests**
- Tests for mobile_qr_live.ex timer cleanup on terminate
- Tests for agent_live index timer cleanup
- Verifies memory leak fixes from previous commits
Files changed:
- lib/towerops/alerts.ex
- lib/towerops/alerts/alert.ex
- lib/towerops/activity_feed.ex
- lib/towerops/workers/device_monitor_worker.ex
- test/**/*_test.exs (44+ files with alert_type references)
- test/towerops_web/live/mobile_qr_live_test.exs
- test/towerops_web/live/agent_live_test.exs
- test/towerops/workers/device_poller_worker_test.exs
All tests passing except 1 unrelated brute force protection test.
- Entity mapping table: hide Gaiia ID and Linked columns on mobile,
show Gaiia ID inline under name on small screens
- Allow text wrapping in name column for long names
- Add overflow-x-auto to table container for safety
- Redirect authenticated users from / to /dashboard instead of /orgs
- Fix CLOAK_KEY placeholder in nix shell (invalid base64)
- Fix error HTML test assertion to match actual template
- Fix Gaiia sync: read 'subnet' field instead of 'block' for IP blocks
- Fix Gaiia sync: extract nested status name from subscription objects
- Fix security headers tests for environment detection
- Fix mobile auth tests to use raw_token
- Fix LiveView test assertions to match actual template content
- Fix SNMP config test expectations for test environment
- Refactor: resolve all Credo complexity/nesting issues
- Extract helper functions in NetBox sync, VISP sync, Sonar sync
- Flatten nested conditionals in settings, integrations, alerts
- Use with/validate_present pattern for connection testing
- Escape HEEx template braces in GraphQL/API docs with raw(~S[...])
- Fix test assertions for updated marketing copy and UI text
- Extract helper functions in GraphQL resolvers to reduce nesting depth
- Create shared ErrorHelpers module for API controllers
- Fix ETS race condition in brute force whitelist cache for async tests
- Fix property test generators to use ASCII instead of printable unicode
- Add alert_severity helper to site_live/show
- Update accounts fixtures for explicit user confirmation
POST /api/v1/webhooks/agent-release triggers all connected agents
to self-update via their existing WebSocket channels. Authenticated
with a shared secret (AGENT_WEBHOOK_SECRET env var).
- Add ReleaseChecker.invalidate_cache/0 for fresh GitHub fetch
- Add Agents.list_updatable_agents/0 (enabled + seen < 10min)
- Add Agents.broadcast_mass_update/0 orchestration function
- Add WebhookAuth plug with timing-safe secret comparison
- Add AgentReleaseWebhookController with :webhook pipeline
- Configure AGENT_WEBHOOK_SECRET in dev/test/runtime configs
Critical fixes:
- Renamed controller actions to follow Phoenix conventions (verify→new, create→verify)
- Added sudo mode check in GET action using last_sudo_at timestamp
- Added TOTP enrollment check in GET action with redirect
- Changed default return path from /orgs to /users/settings
- Updated route paths from /users/sudo/verify to /users/sudo-verify (dash separator)
- Added success flash message "Identity verified."
Implementation details:
- new/2 (GET): Checks recently_verified_sudo?/1 helper that examines last_sudo_at
instead of authenticated_at to distinguish sudo verification from regular login
- verify/2 (POST): Adds info flash and defaults to /users/settings
- recently_verified_sudo?/1: Private helper checking last_sudo_at within 10 minutes
- Updated all routes, templates, and tests to use new paths and action names
Tests:
- Added tests for already-in-sudo-mode redirect behavior
- Added test for TOTP enrollment requirement
- Updated all test assertions for new route paths and success messages
- All 15 controller tests passing
- Full test suite passing (4076 tests, 0 failures)
- Add UserSudoController with GET and POST /users/sudo/verify routes
- Create verify.html.heex template for TOTP verification form
- Only accept TOTP codes (6 numeric digits), reject recovery codes
- Update grant_sudo_mode to set authenticated_at virtual field
- Exclude /users/sudo paths from return_to overwriting
- Add comprehensive controller tests (12 test cases)
- Verify redirect behavior, error handling, and session management
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>