Commit graph

2394 commits

Author SHA1 Message Date
332ee37706 test: GraphLive live mode + Show snmp_sensor format + Alert/Activity events
Bumps coverage by exercising several previously-uncovered branches:

- GraphLive.Show: new test file driving `range=live` for every
  sensor_type (latency, processors, memory, traffic, temperature,
  storage, voltage, count, pppoe_sessions, connections, wireless,
  unknown), plus `get_title_suffix/1` across sensor_id / interface_id
  / storage_id, and `change_range` flipping into live mode.
- DeviceLive.Show: format_check_value/2 now covers the snmp_sensor
  branch across temperature, voltage, current, power, frequency,
  humidity, fanspeed (default + custom unit), and an unknown
  sensor_type with/without a configured unit.
- ActivityFeedLive: clear_search and load_more events; PubSub
  handle_info clauses for new_alert / alert_resolved / device_added
  / config_changed / sync_completed / device_event / :tick / catch-all.
- AlertLive.Index: acknowledge / resolve event handlers across
  success, not-found, and cross-org unauthorized branches.

Coverage: 86.42% → 86.52%.
2026-05-08 18:12:32 -05:00
fc20d60838 test: cover Topology role/link/evidence helper branches
Adds a dedicated test file that drives the small private mapping helpers
inside `Towerops.Topology` via their public surface:

- `role_to_type_atom/1`: every device_role string (router, core_router,
  switch, distribution_switch, access_switch, access_point, cpe,
  backhaul, backhaul_radio, server, firewall, ups, other, unknown
  fallback) is exercised through `device_to_node` (via
  `get_topology_for_map/2`) and again through `get_node_detail/2`.
- `link_type_priority/1`: each priority pair (lldp/cdp/mac_match/
  arp_inference) is exercised via `merge_bidirectional_edges/1` by
  upserting reciprocal links of differing types and asserting the
  higher-priority `link_type` survives the merge.
- `evidence_type_to_link_type/1`: every evidence_type clause
  (lldp_neighbor, cdp_neighbor, mac_on_interface, arp_entry,
  wireless_registration, plus the catch-all) is hit via
  `upsert_link/1` → `process_evidence_for_link/2`.

Coverage: 86.28% → 86.42% (+0.14%).
2026-05-08 17:57:47 -05:00
4ea64cb550 test: lift coverage from 85.84% → 86.28%
Adds focused tests across multiple modules to push toward the 87% threshold:

- CoreComponents: pagination/1 (multi-page, ellipsis, params),
  list/1 (slot rendering), and signal_badge/1 across all RSSI bands.
- ApiDocs/GraphQLDocs controllers: bypass-the-router pattern so the
  "scope-with-organization" branches are actually exercised (the public
  routes are unscoped, so the org branch is unreachable via get/2).
- Mix.Tasks.Unused: text + JSON output, --only / --skip filters.
- Mix.Tasks.JobCleanupTask: prod path with phoenix snmp enabled vs
  disabled (uses :job_cleanup_settle_ms = 0 to avoid the 1s sleep).
- Member resolver: success path for remove/update_role on non-owner
  membership, and the changeset-error branch for an invalid role.
- Check resolver: unauthenticated fallback clauses for list / get /
  create / update / delete.
- RfLinks: nil-signal sort ordering, :unknown classification for
  nil/nil signal+snr, :degraded filter, capacity_utilization edge
  cases (max_rate 0/missing, both rates nil).
- StatusHelpers: status_emoji + title_with_status across green / red
  branches (warning is unreachable given the active-alerts query).
- ApiTokens: spawn_async_update/2 path with env != :test.
- ImpactAnalysis: to_json/1 across nil and populated levels.
- Maintenance: query helpers (default-arity wrappers, active range)
  and Show LiveView delete event redirecting to /maintenance.
- Coverage map: probe_point event handler.
- InvitationQuery: default-arity pending/1.
2026-05-08 17:44:18 -05:00
c50dc08ad4 test: fix slow + flaky tests (Mox global, env pollution, pool starvation)
- Remove Mox.set_mox_global() in DPW extra test; rely on $callers
  instead so concurrent vendor SNMP tests stop intermittently failing
  with VerificationError.
- Make ToweropsWeb.Plugs.GraphQLIntrospectionTest async: false. It
  mutates Application.put_env(:towerops, :env, :prod), which polluted
  every async vendor test that reads :env via
  Towerops.Snmp.Client.phoenix_snmp_disabled/0.
- Bump test pool queue_target/queue_interval. AgentChannelTest spawns
  many `:proc_lib` channel processes that hold sandbox connections;
  the default 50ms queue caused intermittent
  'could not checkout the connection' errors.
- Tag real-System.cmd("ping") tests as :network so they're excluded
  by default. Saves ~20s on every full run.
- Make JobCleanupTask's internal 1s settle sleep configurable via
  :job_cleanup_settle_ms; the test overrides it to 0.

Plus the pending CoverageLive.Form edit-save / EIRP recompute and
TraceLive deep-link tests from before the compact.
2026-05-08 14:43:30 -05:00
c90e34de0b test: DashboardLive acknowledge_alert success and cross-org branches 2026-05-08 14:04:20 -05:00
bcb7882d34 test: redis password branches + suppress 0.0 pattern warnings + coverages from() macro fix 2026-05-08 13:57:07 -05:00
749fc27a80 test: Airfiber detect_version + OnCall.Escalation start_escalation no-policy branches 2026-05-08 13:51:55 -05:00
1b35177c1b test: GaiiaReconciliationLive + ActivityFeedLive helpers and events 2026-05-08 13:39:01 -05:00
76de672ac8 test: parametric HelpLive section coverage + MibCache init/pre_resolve 2026-05-08 13:30:38 -05:00
5af987035f test: GlobalSearch keyboard nav + StatusPageLive mount + Coverages.Profile clutter polygons 2026-05-08 13:23:13 -05:00
09e9fd2f3b test: SiteLive force_rediscover + CoverageLive.Show coverage_status broadcast 2026-05-08 13:16:13 -05:00
67bb67c722 test: GraphQL.Resolvers.Agent direct unit tests 2026-05-08 13:11:02 -05:00
91b0cac164 test: GraphQL.Resolvers.EscalationPolicy + MobileController list_alerts formatting 2026-05-08 13:00:56 -05:00
f8af326deb test: ReportsLive toggle/delete/run_now + UserRegistration invitation paths + SiteLive geocode/apply 2026-05-08 12:53:06 -05:00
3397e1ce40 feat: implement mix_unused-style 'mix unused' task using Erlang xref 2026-05-08 12:30:49 -05:00
bbfdea46e2 test: KMZ controller paths + plug session activity baseline 2026-05-08 12:19:05 -05:00
1634fde7a1 test: SNMP client v3 parsing + AlertNotificationWorker incidents + AgentChannel oversized messages 2026-05-08 11:57:50 -05:00
f9a4611105 test: add query module tests for SiteQuery, AgentTokenQuery, ScheduleQuery, EscalationPolicyQuery 2026-05-08 11:28:25 -05:00
c7c9f893a5 test: add ping/escalation tests; coverage 84.99% → 85.13% 2026-05-08 11:23:08 -05:00
fd75837ae9 test: lift coverage 84.08% → 84.99%; exclude declarative modules from coverage 2026-05-08 11:18:26 -05:00
8b61e1a374 test: add Mox global setup + dummy-sensor gate helper for DPW extra tests 2026-05-08 10:30:06 -05:00
50324c61ce feat(metrics): expose Prometheus /metrics via PromEx on :9568
Add PromEx with Application/BEAM/Phoenix/LiveView/Ecto/Oban plugins plus
a custom plugin that bridges existing Towerops Oban/Redis telemetry events.
The metrics HTTP server runs isolated on port 9568 so scrape traffic never
traverses the public Traefik IngressRoute.

The pod template carries prometheus.io annotations (scrape, port, path, job)
so the external Prometheus on 10.0.15.31 discovers each pod through the
existing kubernetes-pods scrape job (apiserver-proxy).
2026-05-08 10:25:25 -05:00
027b532a95 test: more coverage across LiveViews (gaiia mapping, schedule, integrations) 2026-05-08 09:58:10 -05:00
53f24bb95e test: lift coverage 81.88% → 82.45% with more agent-driven tests
- Snmp.Discovery.create_checks_from_discovery: zero-data path covered;
  full-data path tagged :skip (existing lib/ has KeyError on processor_descr/
  sensor_class — real bug, lib unchanged per scope)
- Proto.Decode (new file): round-trip + validation for decode_agent_heartbeat,
  decode_metric_batch, decode_snmp_result, decode_agent_error,
  decode_credential_test_result, decode_mikrotik_result,
  decode_monitoring_check, decode_lldp_topology_result, decode_check_result
- DeviceLive.Show.events (new file): 39 tests covering checks/backups/preseem/
  gaiia tabs, set_capacity, agent handle_info handlers, FormComponent
  callbacks. Coverage 64% → 77.6%.
- GraphLive.Show.events (new file): check-based graph paths, range switching
  across all valid ranges, less-common sensor types, access control
  redirects. terminate-cleanup test tagged :skip.
- Org.SettingsLive.more_events (new file): more handle_event branches for
  integrations, set_netbox_sync_direction, default_org handling.
2026-05-08 09:45:15 -05:00
dcd75e55be test: AgentChannel processing tests + skip flaky discovery/ping tests 2026-05-08 09:34:10 -05:00
8fe1850d8a test: lift coverage 79.78% → 80.92%+ with broad new tests
Adds 100+ new tests across:

- Snmp.Discovery sync_*: mempools/transceivers/printer_supplies/
  entity_physical/save_neighbors/save_arp_entries
- Topology: compute_wireless_stats, compute_rf_link_stats,
  get_topology_for_weathermap, LLDP discovery/list/upsert,
  remove_stale + find_device_by_name fallthroughs
- Trace: account/inventory/access_point assemble_trace + trace_from_device
  inventory→account linking
- DiscoveryWorker: assigned-agent fallback, agent offline mid-discovery,
  device deleted during discovery, perform/1 rescue (34% → 64%)
- Snmp.Profiles.Base: discover_state_sensors/vlans/ipv6_addresses/
  memory_pools/processors + identify_device for many vendors
- ChartBuilders: load_*_chart_data nil/empty + DB-backed sensor charts
- OrgSettingsLive events: apply_snmp/apply_agent/toggle_default/
  toggle_enabled
- DeviceLive.Form events: edit access errors, validate branches,
  test_snmp w/o agent + with v2c/v3 credentials, query param prefill
- DeviceLive.Index: reorder, search, filter_status, force rediscover
- CheckLive.FormComponent: full mount/change_type/validate/save flow
  for http/tcp/dns/ssl + edit/close
2026-05-08 09:11:06 -05:00
e688d4d9f2 test: cover Snmp.Discovery sync_processors/sync_storage upsert paths
New file: discovery_sync_test.exs

- sync_processors/2: insert, remove, update happy paths + empty-list no-op
- sync_storage/2: insert + remove + update across two sync calls
2026-05-07 19:35:23 -05:00
4b2bc4f739 test: lift coverage 79.59% → 79.85% with more focused tests
- TraceLive.Index: mount + search/select/clear handlers + format helpers
- MikrotikBackupLive.Compare: full mount/handle_params/download_config flow
- ConfigTimelineLive: redirect + render + select_range/close_event
- Admin.UserLive.Index: impersonate redirect + delete_user happy/error
- Snmp.Vlans: list/get coverage
- DnsExecutor: AAAA/MX/TXT/CNAME query branches
- PagerDuty.Client: device_up/unknown alert_type severity branches
- Resolvers.Device: create/update/delete/metrics/interfaces happy + error
2026-05-07 19:16:13 -05:00
2269f38fc5 test: lift coverage 78.42% → 79.59% with focused unit + integration tests
Adds tests across previously-uncovered or under-covered modules:

- Mix tasks: backfill_checks, copy_mibs, import_mibs (new test files)
- Status pages: StatusIncident changeset (new file)
- Webhook signature verification on AgentReleaseWebhookController
- CloudflareBanWorker: HTTP-stubbed success / 5xx / transport-error paths
- FirmwareVersionFetcherWorker: stubbed perform/0 success + non-200 + tx error
- Geoip.Import: production --production code path (HTTP-stubbed)
- AgentLive.Show: superuser restart_agent + update_agent + non-superuser paths
- NetworkMapLive: node_clicked, deep-link node, topology_updated PubSub
- PreseemInsightsLive: toggle_select, deselect_all, filter, dismiss-twice
- AlertQuery: 1-arity defaults variants
- ProfileWatcher: yaml + reload-trigger event passthrough
- MobileAuthController: verify_qr_token success + get/revoke session
- HealthController: redis-configured branch
- RedisHealthCheck: un-tag :integration tests now that Redis is local
- FourOhFourTracker: un-skip module (Redis available)
- PingExecutor: un-tag local-only tests + KeyError surface + clamp branch
- CheckExecutorWorker: dispatch tcp/dns/ssl/ping branches
- UserResetPasswordController: drop dead edit/update + their template

Also removes dead code: edit/update actions on UserResetPasswordController
and the unused edit.html.heex template — both routed via LiveView.
2026-05-07 18:40:43 -05:00
22f5c3ed63 test: lift coverage to 78.42% and link Ansible collection in docs
- Add Codeberg Ansible collection link to API/GraphQL docs and Terraform guide
- Un-skip the previously-disabled DevicePollerWorker tests that still pass
- Expand MibController, CnMaestro.Sync, CheckWorker, ActivityFeed,
  MobileController, JobCleanupTask tests
- New UploadMibs Mix task test covering arg validation and upload paths
- Set :mib_dir in test config so MibController upload/delete flows can
  exercise the real on-disk paths against a writable tmp directory
2026-05-07 16:48:53 -05:00
973c08bc5c docs(api): add Coverages, Maintenance Windows, Schedules, Escalation Policies
The /docs/api and /docs/graphql pages were missing four resource
families that the API has shipped:

- Coverages (RF coverage prediction with async compute, recompute,
  KMZ download)
- Maintenance Windows (one-off + recurring with RRULE)
- On-Call Schedules (top-level CRUD + layers, members, overrides,
  on_call lookup)
- Escalation Policies (CRUD + rules + targets)

Both REST and GraphQL pages now have sidebar entries and example
requests for each.
2026-05-07 16:08:09 -05:00
27e8eb316f test(api_docs): assert page renders rather than checking for sample_token
The current ApiDocs/GraphQLDocs templates don't surface the sample_token
back into the rendered HTML, so the previous assertions were checking
for strings that don't exist. Drop them down to a 'page renders' check.
The controller branches that compute sample_token still get exercised.
2026-05-07 15:57:36 -05:00
cc40b14afd test: expand UserRegistrationLive form events + normalize_consent_params 2026-05-07 14:35:56 -05:00
bc462c5831 test: more LiveView event coverage (TotpManager, ApiTokenManager, OrgSettings) 2026-05-07 14:32:35 -05:00
2e399953c1 test: expand coverage across resolvers, vendors, controllers, workers
Add focused tests across many modules to push overall coverage from
75.45% to ~77%.

New test files:
- test/towerops/snmp/topology_test.exs
- test/towerops/vault_test.exs
- test/towerops/workers/check_worker_test.exs
- test/towerops_web/graphql/resolvers/happy_path_test.exs
- test/towerops_web/graphql/schema_test.exs
- test/towerops_web/live/reports_live_test.exs

Expanded existing tests for: Airos vendor, ProfileWatcher, StormDetector,
LLDP, GpsSync, MikrotikBackupWorker, AdminController, MibController,
MobileController, GeoipController, OnboardingLive, UserResetPasswordLive,
SessionManager, Telemetry, CoverageLive.Show.

Also fix a pre-existing dead test in ApplicationSettingTest where the
schema default for value_type made the 'invalid without value_type' test
unreachable.
2026-05-07 14:23:58 -05:00
f36315acf9 tests 2026-05-07 13:14:02 -05:00
03ec909e3a test: add LiveView tests following testing-liveview patterns
Apply Testing LiveView course patterns (live/2, has_element?,
render_click, form/3). Add tests for MobileQRLive, WeathermapLive,
StatusPageLive, controllers, plugs, and pure function modules.
Expand proto decode coverage to 85.24%.
2026-05-07 12:55:39 -05:00
7aca7a4862 test: increase coverage from 74.24% to 74.84%
Add tests for proto decode/encode validation edge cases, assert/401 plugs,
authorization policy, identifier normalization, telemetry handlers, GeoIP
parsing, agent helpers, maintenance helpers, and Ecto query modules.
2026-05-07 11:20:29 -05:00
7adc2fb0a6 test(coverage): add tests for index/form events, raster, buildings, context
Push the Coverages namespace coverage up further:

* CoverageLive.Index — delete and recompute event handlers, the
  in-progress flash branch, status_badge_class fallback. 35% → 87%.
* CoverageLive.Form — invalid-save round-trip, location_picked and
  use_site_location event handlers. 61% → 77%.
* Coverages.Raster — query_rssi guard branches (not_ready, outside_bbox,
  raster_missing, missing-bbox), cleanup no-op, and Lidar.Reader
  parse_aaigrid header parsing. 63% → 75%.
* Coverages.Buildings — to_clutter height fallback, polygon coord
  extraction, for_bbox PostGIS intersect against real DB rows. 43% → ~95%.
* Coverages context — topic helpers, subscribe + broadcast,
  list_ready_for_organization, change_coverage, query_point happy and
  empty paths, queue_compute idempotency. 65% → 93%.

Total project coverage 74.05% → 74.25%.
2026-05-07 09:36:16 -05:00
1b7312a8df test: add settings session/TOTP manager unit tests 2026-05-07 09:25:30 -05:00
0e2cc6b7ce chore(coverage,types): clean dialyzer + new tests + ETH canopy provider
Dialyzer:
* Add Lidar.Tile @type, narrow several @specs to match success-typing,
  prefix discarded function returns with `_ =`, fix the broken
  File.stream! call in the ms-buildings worker (it was causing dialyzer
  to mark every private fun unreachable), broaden Antenna.spec key
  types from `float()` to `number()` to match catalog int literals.
* Add :geo, :geo_postgis, :db_connection to plt_add_apps so the dep
  PLT actually contains the modules we use.
* Result: 0 unsuppressed dialyzer warnings (was 50+ in project code).

Tree canopy:
* Replace the dead landfire.gov URL with the ETH Zurich Global
  Canopy Height 10 m product. ETH publishes proper COGs with
  overviews on libdrive.ethz.ch — confirmed working with
  /vsicurl/ byte-range reads. The module now picks the 3°×3° tile
  containing the bbox centroid, with both a `:metres` decoder
  (ETH Byte raster, NoData=255) and a `:landfire_evh` decoder for
  operators self-hosting LANDFIRE EVH COGs.
* Re-enable canopy by default now that there's a working source.

Tests (+105 tests, 0 failures):
* CoverageLive Index/Show/Form/Map — empty/loaded/auth states,
  LiveView event handlers, formatters.
* Coverages.Building changeset + helpers.
* Coverages.TreeCanopy provider tile-URL builder and decoders.
* Workers.MsBuildingsImportWorker (full import flow incl. blank
  lines, MultiPolygon, idempotency, hash fallback) — also fixed
  the partial-index ON CONFLICT bug that prevented imports from
  ever working: the unique index has WHERE ms_footprint_id IS
  NOT NULL, so the worker now uses an :unsafe_fragment conflict
  target that includes the predicate.
* GraphQLDocsController smoke test (was 0%).

Coverage rose from 73.19% → 74% with the major project modules I
touched all moved from 0% / <50% to >75%.
2026-05-07 09:11:17 -05:00
4e391b3ecf feat(coverage): cnHeat-style full-bleed map on the show page
Rewire the single-coverage show page to mount MultiCoverageMap (the
same hook the org map uses) with one coverage in the array, and lift
the cnHeat-style overlay panels — LOS/NLOS toggle + height slider on
the left, opacity + RSSI filter on the bottom, distance probe on
click. Parameters move into a togglable right-side drawer so the map
dominates the viewport.

Show LiveView gains toggle_params / probe_point / close_probe /
set_probe_units handlers and delegates the formatters to
CoverageLive.Map. Site is now preloaded so coverages_payload/1 can
read site.name without an Ecto.AssociationNotLoaded crash that was
silently nuking the data-coverages JSON encode.

Also fixes a real bug in coverage_hooks.ts: pngForCoverage was
returning before the antenna-marker creation block, leaving the
marker code as unreachable dead code — towers never rendered. Moved
the marker creation into addCoverageLayer where it belongs and added
a no-PNG guard so a missing png_path logs instead of silently
failing the L.imageOverlay constructor.
2026-05-07 08:27:27 -05:00
98da879399 fix(config-timeline): implement the missing chart hook
ConfigTimelineLive's chart panel was rendering a bare 'Loading chart...'
placeholder forever — the phx-hook="ConfigTimelineChart" had no JS
implementation registered anywhere, so the whole point of the page
(correlating QoE regressions with config edits) was lost.

Add a focused lazy hook in hooks/config_timeline_chart.ts that:

  * Plots Preseem latency / throughput / loss across three Y-axes.
  * Overlays vertical dashed markers at each config-change timestamp
    with a small badge showing how many lines changed.
  * Drops a red triangle on the time axis for every failed check.
  * Reuses the Chart.js vendor chunk (esbuild --splitting factors it
    into a shared chunk with sensor_chart, so visiting either page
    primes the other).

Bundle delta:
  chunk-RSDHLN2L.js (Chart.js, shared)   314.8 KB
  config_timeline_chart                    7.2 KB
  sensor_chart (was 325 KB standalone)    10.6 KB after dedup
2026-05-07 08:11:48 -05:00
7ffc4ebd2b perf(assets): lazy-load three more hooks + trim CopyToClipboard
DeviceListReorder, SortableList, MikrotikPortSync are each used on a
single page (devices index, escalation/schedule show, device edit
form respectively) — push them out of app.js into their own chunks
via lazyHook. Also extract MikrotikPortSync into its own file under
hooks/ for consistency.

Compress CopyToClipboard's flash feedback by collapsing the duplicated
success/error inline-SVG/setTimeout blocks into one helper, drop the
debug console.logs, and remove the bulk Phoenix-template boilerplate
comment block that was just generated noise.

After-bundle (minified):
  app.js                 287.6 KB   (-2 KB on top of prior split)
  device_list_reorder      4.6 KB   loaded on /devices only
  sortable_list            3.1 KB   loaded on escalation+schedule pages
  mikrotik_port_sync       1.3 KB   loaded on device edit form only
2026-05-07 08:05:22 -05:00
3bd4ed303c perf(assets): split big hooks into per-page chunks
app.js was bundling Chart.js (~200 KB), Cytoscape (~500 KB) and four
heavy hooks (SensorChart, NetworkMap, WeathermapViewer, SitesMap)
into a single 1.25 MB blob that every page paid for.

Extract each into its own module under assets/js/hooks/ and reference
them through the existing lazyHook() wrapper so the implementation is
fetched only when a hook actually mounts. esbuild --splitting then
factors the shared cytoscape vendor blob into a chunk that's only
loaded on topology / weathermap pages.

After-bundle layout (minified):
  app.js                  297.8 KB   (was ~1250 KB; -76%)
  chunk-LJGWK7RH (cyto)   555.1 KB   loaded on topology pages only
  sensor_chart            325.3 KB   loaded on sensor pages only
  network_map              24.4 KB   topology hook glue
  weathermap               13.0 KB   weathermap hook glue
  sites_map                 4.1 KB   site map hook glue
  coverage_hooks           25.0 KB   coverage hook glue

LeafletMap also moved to sites_map.ts and now uses ensureLeaflet
instead of polling for window.L with setTimeout.
2026-05-07 07:58:38 -05:00
38375dcebb chore: migrate agent repo references from GitHub to Codeberg
Updates all docs, UI copy, release checker, proto go_package,
container image refs, and tests to point at
codeberg.org/towerops-agent/towerops-agent. Also deletes stray
empty package.json/package-lock.json (Phoenix uses esbuild — no
npm) and an unused test_encode_decode.exs scratch file.
2026-05-07 07:44:48 -05:00
4a8d8cb208 feat(coverage): write rasters to NFS-backed dir, serve via dedicated static plug
Coverage rasters were being written to priv/static/coverage on the pod's
ephemeral filesystem — fine on a single-pod dev box, broken under k8s
where pods share NFS at /data and a restart wipes computed predictions.

* New :coverage_storage_dir config (defaults to priv/static/coverage for
  dev/test, "/data/coverage" in production).
* Raster.output_dir/absolute_raster_path read this config and resolve
  {otp_app, rel} or absolute paths.
* Endpoint now mounts a dedicated Plug.Static at /coverage from that
  same config — replicas all serve the same NFS-backed file regardless
  of which one ran the compute.
* Drop "coverage" from static_paths/0 since the dedicated plug owns
  that prefix now.
2026-05-07 07:38:05 -05:00
f266b4ed34 fix(coverage): serve generated coverage PNGs via Plug.Static
`coverage` was missing from static_paths/0, so /coverage/<org>/<id>/rssi*.png
fell through to the router (or downstream CDN) instead of being served as a
static asset. In prod that meant the ingress's hotlink-protection page was
rendered as the Leaflet image overlay — the "Access blocked / Referer is
required by..." graphic the user saw on the map.
2026-05-07 07:32:14 -05:00
16e4997368 fix(coverage): structured stage logging + per-row hang detection
CoverageWorker was silent between progress updates, so a stuck
gdalwarp / runaway pixel loop showed up only as 'stuck at 15%' in
the UI with nothing in k8s logs. Now every stage logs with the
coverage id (terrain fetch, building load, per-tier LOS+NLOS, row
band progress every 10%, finalize) and the row stream uses a
10-minute timeout with on_timeout: :kill_task so a hang surfaces
as an explicit row-failed log line + NaN fill instead of a wedged
worker. Unexpected exceptions now log with stacktrace and fail the
coverage cleanly.
2026-05-06 17:43:16 -05:00
cb04cdd1d9 fix(coverage): align progress percentage with bar, not title 2026-05-06 17:38:32 -05:00