Commit graph

271 commits

Author SHA1 Message Date
997bc46cbf fix: dns and ping checks not executing in production (#71)
## Summary

- **Ping checks never scheduled**: `ensure_default_ping_check` created check records but never called `schedule_check`, so auto-created ping checks never executed
- **Skipped checks permanently orphaned**: `CheckExecutorWorker` only rescheduled checks that actually executed — skipped checks (agent-assigned, SNMP disabled) broke the scheduling chain permanently
- **No recovery mechanism**: `JobHealthCheckWorker` only recovered `DeviceMonitorWorker` jobs, not `CheckExecutorWorker` jobs — any lost check job was gone forever

## Changes

- `monitoring.ex`: Call `schedule_check` after creating ping check in `ensure_default_ping_check`
- `check_executor_worker.ex`: Reschedule skipped-but-enabled checks so conditions are re-evaluated next cycle
- `job_health_check_worker.ex`: Recover orphaned `CheckExecutorWorker` jobs for enabled checks (runs every 10 min)
- `devices.ex`: Wrap `ensure_default_ping_check` in try/rescue for deadlock resilience

## Test plan

- [x] New tests: ping check scheduling on creation, no duplicate scheduling on idempotent call
- [x] Updated tests: skipped checks now assert rescheduling instead of orphaning
- [x] New tests: health check worker recovers orphaned check executor jobs, skips disabled checks
- [x] All 177 targeted tests pass
- [x] Compile clean with warnings-as-errors
- [x] Pre-commit hooks pass (credo, format)

Reviewed-on: graham/towerops-web#71
2026-03-18 08:43:37 -05:00
c8a092196f add auto ICMP ping check for all devices (#68)
## 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
2026-03-17 17:55:23 -05:00
76b3dd6a93 add ssl/tls certificate expiration check type (#64)
add ssl check executor that connects via TLS, reads peer certificate
expiry, and returns OK/WARNING/CRITICAL based on configurable
warning_days threshold. includes protobuf config, liveview form fields,
agent channel encoding, oban worker dispatch, and unit tests.

Reviewed-on: graham/towerops-web#64
2026-03-17 16:14:22 -05:00
28e97ff5f0 add service checks REST API, GraphQL API, and ping executor (#52)
- 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
2026-03-16 18:40:18 -05:00
42bfc8b9d2 paperclip/TOW-21-uisp-integration-phase1 (#31)
Reviewed-on: graham/towerops-web#31
2026-03-15 17:57:36 -05:00
acf70b3bee
fix: remove duplicate StormDetector child spec
The StormDetector was being added twice:
1. In the main children list (line 127)
2. In background_workers() (line 176)

This caused the supervisor to fail with 'duplicate_child_name' error.

Removed the duplicate from main children list since it's correctly
placed in background_workers() which also handles test env properly.
2026-03-15 16:05:30 -05:00
90af7ac0d1 feat: alert storm correlation & suppression (TOW-13)
- StormDetector GenServer: sliding-window per org, >10 alerts/min triggers
  suppression mode with single summary notification
- Site correlation: >3 devices at same site down within 120s creates
  single 'Site Outage' alert instead of N individual alerts
- Notification rate limiter: max 5 per user per 15min window, excess
  batched into digest via AlertDigestWorker
- PagerDuty client: retry with exponential backoff on 429, respects
  Retry-After header
- Batch maintenance checks: new Maintenance.devices_in_maintenance/1
  takes list of device_ids, returns MapSet (single query vs N queries)
- Migration: alert_storms, site_outages, notification_digests tables
  plus site_outage_id/storm_suppressed on alerts
- DeviceMonitorWorker integrates storm detection and site correlation
  before creating alerts
- Tests for StormDetector, SiteCorrelation, NotificationRateLimiter,
  and batch maintenance checks
2026-03-15 14:29:12 -05:00
a1c5d593dc
fix: network map label sizes, site layout, and missing links
- Increase node font from 11px to 13px, compound labels to 14px
- Replace cose physics layout with deterministic grid preset layout to eliminate site group overlap
- Fix nil source_interface_id crash in topology.ex find_existing_link/1 (was using == nil instead of is_nil/1)
- Fix nil device label in device_to_node/1 fallback to IP or "Unknown"
2026-03-14 18:27:54 -05:00
37314bd154 Add alert routing setting (builtin/pagerduty/both)
- New 'alert_routing' field on organizations (default: builtin)
- AlertNotificationWorker respects the setting:
  - builtin: only built-in escalation policies
  - pagerduty: only PagerDuty events
  - both: fire both (useful during migration)
- Radio button UI in Settings → General with visual cards
- Warning shown when PagerDuty selected but not configured
- Migration, schema validation, 6 tests
2026-03-13 15:26:25 -05:00
19abd5508a
ux: improve mobile responsiveness across all pages
- Hide less-critical table columns on small screens (IP, subs, MRR, etc.)
- Add overflow-x-auto wrappers on tables to prevent horizontal scroll
- Fix <.header> component to flex-wrap action buttons on mobile
- Make page action button labels icon-only on small screens
- Add min-w-0 on grid children to prevent viewport overflow
- Right-align device detail dt/dd values consistently
- Fix schedule timeline cards with overflow-x-auto wrappers
2026-03-12 16:49:59 -05:00
72f81c572d
add real-time GraphQL API with time-series queries and subscriptions
- add absinthe_phoenix for WebSocket subscription support
- create GraphQL WebSocket socket with API token auth at /socket/graphql
- add time-series queries: deviceSensors, sensorReadings, interfaceTraffic, checkResults
- add subscriptions: deviceStatusChanged, alertEvent, sensorReadingsUpdated
- wire subscription triggers into agent_channel and alerts contexts
- add org-scoped access control for sensors/interfaces via join queries
- add 17 tests covering queries, org isolation, empty data edge cases, and auth
- update GraphQL API docs page with time-series and subscription sections
2026-03-12 10:24:39 -05:00
5026614bf3
fix: nav overflow on small screens and fix bad gettext translations
Bump desktop nav breakpoint from md (768px) to lg (1024px) so the
mobile hamburger menu is used on medium-width screens instead of
nav items overflowing outside the viewport.

Clear all 65 bad fuzzy translations in English .po files (e.g.
"Navigation" was incorrectly translated to "Pagination") and fix
49 fuzzy entries in Spanish .po files with correct translations.
2026-03-11 14:00:31 -05:00
0cd2ed3567
feat: add on-call schedules and escalation policies
Built-in PagerDuty-equivalent system for on-call scheduling and alert
escalation. Users can now manage schedules, rotation layers, overrides,
and escalation policies directly in the app alongside PagerDuty.

- On-call schedules with rotation layers (daily/weekly/custom), member
  management, and temporary overrides
- Escalation policies with ordered rules, timeout-based escalation,
  and user/schedule targets
- Automatic escalation via Oban worker with configurable repeat count
- Email notifications via Swoosh for on-call alerts
- Resolver computes who's on-call from layer stacking and overrides
- AlertNotificationWorker integration: starts escalation alongside
  PagerDuty, acknowledges/resolves incidents on alert state changes
- Device and organization schemas support escalation_policy_id
- Escalation policy picker on device form
- Schedules nav item with tabbed index (schedules + escalation policies)
- Full CRUD UI for schedules, layers, members, overrides, rules, targets
- 62 LiveView tests, 56 context/schema/resolver/escalation tests
- 26 E2E Playwright tests for smoke and critical path coverage
2026-03-11 12:32:54 -05:00
bdbe809bab
fix: correct fuzzy gettext translation showing "Admin Dashboard" in nav 2026-03-10 13:50:43 -05:00
f09d1b296d feat: add historical wireless client tracking with TimescaleDB
- Create wireless_client_readings hypertable with compression and retention
- Add WirelessClientReading schema and batch insert function
- Update DevicePollerWorker to save historical readings
- Add signal_badge and snr_badge components to CoreComponents
2026-03-09 17:42:17 -05:00
3c0157d2ea
docs: update user-facing changelog with recent UI and feature improvements
Added missing entries:
- UI button styling improvements (2026-03-09)
- Backhaul capacity analysis feature (2026-03-07)
2026-03-09 15:53:37 -05:00
d811bcb503
feat: add ErrorTrackerNotifier and fix PagerDuty notification handling
- Add error_tracker_notifier for email alerts on exceptions
  * Sends alerts to graham@towerops.net
  * Configured in production only (gracefully shuts down in dev/test)
  * Includes throttling to prevent duplicate error spam

- Fix AlertNotificationWorker when PagerDuty not configured
  * Worker now handles :not_configured gracefully instead of failing
  * Prevents unnecessary retries and error noise
  * Logs as debug when PagerDuty integration isn't set up

Files changed:
- mix.exs, mix.lock
- config/runtime.exs
- lib/towerops/application.ex
- lib/towerops/workers/alert_notification_worker.ex
- CHANGELOG.txt, priv/static/changelog.txt
2026-03-09 13:14:30 -05:00
90fb424f34
feat: create test organization in e2e seed script
After login, users without an organization are redirected to /orgs instead
of /dashboard. The e2e tests expect /dashboard, so create a test organization
for the user during seeding.

Also made the seed script idempotent by deleting existing test user before
creating a new one.

Tested locally and confirmed working.
2026-03-08 13:14:56 -05:00
97ba2b045b
fix: use correct signature for create_totp_device/3
Function signature is create_totp_device(user_id, device_name, secret) with
separate arguments, not create_totp_device(user, map).

Tested locally and confirmed working.
2026-03-08 12:42:17 -05:00
ae125f380c
fix: use create_totp_device/2 instead of create_totp_credential/2
The function is create_totp_device/2, not create_totp_credential/2.
2026-03-08 12:17:55 -05:00
9e236225b1
fix: use direct changeset to confirm test user email
Accounts.confirm_user_email/1 doesn't exist - the module only has confirm_user/1
which requires a token. For seeding, we can directly set confirmed_at timestamp
using Ecto.Changeset.change/2.
2026-03-08 11:53:47 -05:00
e7a25a5f2c
fix: add required consent fields to e2e test user registration
User registration requires terms_of_service_consent and privacy_policy_consent
to be true. The seed script was failing without these fields.
2026-03-08 11:20:14 -05:00
a102918455
feat: add e2e test user seeding for CI
Created seeds_e2e.exs to automatically create a test user with TOTP enabled
for Playwright e2e tests. This ensures tests can authenticate in CI without
requiring manual user setup.

Test credentials (for CI only):
- Email: test@example.com
- Password: TestPassword123!
- TOTP Secret: JBSWY3DPEHPK3PXP (fixed for reproducibility)

The workflow now:
1. Loads database structure
2. Seeds test user with TOTP
3. Passes credentials to e2e tests via environment variables
2026-03-08 10:57:18 -05:00
3913981094
feat: add backhaul capacity analysis foundation
Add per-interface capacity tracking with manual override and automatic
detection from SNMP sensors. Includes capacity resolver with priority
cascade (manual > sensor > if_speed), throughput calculation from
interface stats, and site/organization-level capacity summaries.

- Migration: add configured_capacity_bps and capacity_source to snmp_interfaces
- Capacity.Resolver: multi-source capacity detection with vendor unit normalization
- Capacity context: throughput calculation, utilization percentages, site/org summaries
- Snmp context: set_manual_capacity/2 and clear_manual_capacity/1 functions
2026-03-06 17:54:32 -06:00
d5b0f38ed6
feat: add remaining e2e tests and fix 'too many open files' error
- Added e2e tests for auth flows (registration, login, password reset, TOTP)
- Added e2e tests for onboarding and organization creation
- Added e2e tests for team collaboration (org switching, invitations)
- Added e2e tests for activity feed
- Added e2e tests for device graphs (sensors, time ranges, interfaces)
- Added e2e tests for MikroTik backup comparison
- Added e2e tests for sites map (geographic view)
- Added e2e tests for network trace tool

- Fixed 'too many open files' error by configuring Phoenix code reloader
  to ignore deps/, _build/, node_modules/, e2e/, and other non-source directories
- This prevents the file system watcher from monitoring unnecessary files
2026-03-06 17:08:27 -06:00
a2ce5817a2
fix: use transparent background for stoplight favicon
Replace white background with transparent for better appearance
in browser tabs with different background colors.
2026-03-06 14:46:02 -06:00
e278d320ce
design: improve stoplight favicon with transparent background
Make circles more prominent with transparent background and better spacing.
2026-03-06 14:36:32 -06:00
45579813bd
design: update favicon to stoplight style with app colors
Replace standard favicon with stoplight design using red (#ef4444),
yellow (#eab308), and green (#22c55e) circles matching the app's
status indicator colors.
2026-03-06 14:33:29 -06:00
ac2547e7c9
Merge branch 'feature/dynamic-global-pricing' 2026-03-06 14:04:12 -06:00
6e2bba4e88
docs: update changelogs for dynamic pricing feature 2026-03-06 14:00:58 -06:00
6d3fc15f4a
fix: allow data URLs in CSP for dynamic favicon
The dashboard status indicator favicon (colored circle) uses canvas
to generate a data URL that's set as the favicon. This was blocked
by CSP in staging/production because default-src didn't include data:.

Added data: to default-src directive to fix favicon not updating in
browser tabs when device status changes.
2026-03-06 13:51:31 -06:00
448ed20401
feat: dynamic global billing defaults from ApplicationSettings 2026-03-06 13:44:03 -06:00
7d61d8fb0a
feat: seed global billing settings in application_settings 2026-03-06 13:26:50 -06:00
9229e6e920
fix: remove internal details from public changelog 2026-03-06 13:03:36 -06:00
e161aaab83
docs: update changelogs for billing overrides and interface stats fix 2026-03-06 13:02:55 -06:00
9aec87636b
feat: per-organization billing override admin UI
Allow superadmins to override free device limits and per-device pricing
on a per-organization basis. Overrides cascade through subscription
limits, billing calculations, and user-facing settings display.

- Add custom_free_device_limit and custom_price_per_device to organizations
- Add billing_override_changeset with validation
- Update SubscriptionLimits.effective_device_limit to respect overrides
- Update Billing to use effective free count and price per device
- Add Admin.update_billing_overrides with audit logging
- Add override editing UI to /admin/organizations
- Update org settings page to show effective limits/pricing
2026-03-06 13:02:05 -06:00
d17b7baba4
fix: correct 'More' navigation translation from 'or' to 'More' 2026-03-06 12:26:18 -06:00
b3cff9afbb
stripe and email after signup 2026-03-06 10:07:27 -06:00
8f52d87854
feat: add rate limiting to admin endpoints (100 req/min)
- Add :admin type to RateLimit plug with 100 requests per minute per IP
- Apply rate limiting to all /admin routes (LiveView and controller actions)
- Protects superuser endpoints from abuse and brute force attempts
- Aligns with existing auth (10 req/min) and API (1000 req/min) limits
2026-03-05 14:11:19 -06:00
8f31219d85
fix: add --skip-if-loaded to test alias to prevent prompts
The test alias was prompting 'Are you sure you want to proceed?' when
the database structure was already loaded, requiring manual confirmation
on every test run.

Add --skip-if-loaded flag to ecto.load command so it silently skips
structure loading if schema_migrations table already exists.

Now 'mix test' runs without user interaction.
2026-03-05 13:49:51 -06:00
ade7e2fe15
fix: correct mail adapter message interpolation in login page
Fixes fragmented translation that displayed raw '%{link}' placeholder text
instead of properly interpolated link in development mail adapter notice.

Changes:
- Consolidate separate translation calls into single interpolated string
- Use raw() helper to safely inject HTML link into translated text
- Update Spanish translation to include %{link} placeholder
- Extract updated translations with mix gettext.extract

Before: "To see sent emails, visit %{link}. the mailbox page."
After: "To see sent emails, visit the mailbox page." (with proper link)
2026-03-05 13:32:34 -06:00
7607a583cf
perf(ci): optimize database setup with structure.sql (99.7% faster)
Replace sequential migration execution (4m8s for 172 migrations) with
structure.sql loading (416ms), reducing CI database setup time by 99.7%.

Changes:
- Configure Ecto to dump/load schema from priv/repo/structure.sql
- Update test alias: ecto.migrate → ecto.load
- Remove redundant database setup step from CI workflow
- Commit structure.sql to repository for version control

Benefits:
- CI database setup: 4m8s → 416ms (248s → 0.4s)
- Consistent schema snapshots across environments
- Faster local test runs

Maintenance:
- Run 'mix ecto.dump' after adding new migrations
- Structure file auto-updates with schema changes
2026-03-05 13:30:52 -06:00
1d928d4356
security: implement comprehensive security audit fixes
Critical Fixes:
- Remove /health/time endpoint exposing system time information
  * Prevents attackers from detecting time sync issues for TOTP attacks
  * Removed route and controller function, updated tests
- Add email confirmation check to account data export
  * GDPR export now requires confirmed email address
  * Prevents unconfirmed accounts from accessing data export
- Add path traversal validation for MIB archive uploads
  * Extract to temp directory, validate all paths, then copy if safe
  * Prevents malicious tar/zip files from writing outside target directory
  * Added validate_extracted_paths/1 helper function

High Priority Fixes:
- Add comprehensive input validation for mobile auth
  * Length limits: device_name (255), device_os (100), app_version (50), push_token (512)
  * Prevents database corruption and storage exhaustion
- Add heartbeat rate limiting to agent channel
  * Limit database updates to once per 30 seconds (max ~2/min per agent)
  * Prevents malicious agents from exhausting database connections
- Sanitize 500 error responses
  * Return generic error messages to clients
  * Log full details server-side with request_id for support
  * Prevents leaking stack traces and module names
- Add message size limits to agent channel
  * 10MB maximum for all protobuf messages (result, heartbeat, error)
  * Prevents DoS attacks via oversized payloads

Medium Priority Fixes:
- Add GraphQL query depth limits (max_depth: 10)
  * Prevents DoS from deeply nested queries
  * Complements existing complexity limits

Code Quality:
- Refactor agent channel handlers to reduce nesting depth
  * Extract message processing into separate private functions
  * Fixes Credo warnings about excessive nesting
  * Improves code readability and maintainability

Files changed:
- lib/towerops_web/controllers/health_controller.ex
- lib/towerops_web/controllers/api/account_data_controller.ex
- lib/towerops_web/controllers/api/v1/mib_controller.ex
- lib/towerops/mobile_sessions/mobile_session.ex
- lib/towerops_web/channels/agent_channel.ex
- lib/towerops_web/controllers/error_json.ex
- lib/towerops_web/router.ex
- CHANGELOG.txt
- priv/static/changelog.txt
- test/towerops_web/controllers/health_controller_test.exs
- test/towerops_web/controllers/error_json_test.exs

All 7,424 tests passing.
2026-03-05 13:08:10 -06:00
ed4fce49a4
fix: ensure alerts table columns exist
- Add idempotent migration to ensure severity, notification_sent, etc. columns exist
- Fixes production issue where migration was marked as run but columns weren't created
- Uses IF NOT EXISTS to safely add missing columns without errors
- Includes all columns and indexes from ExtendAlertsForChecks migration
2026-03-05 12:41:56 -06:00
e0757fcd6a
feat: add LLDP topology discovery support from agent
Integrates LLDP neighbor discovery from towerops-agent into Phoenix:

- Updated protobuf definitions with LldpTopologyResult and LldpNeighbor messages
- Regenerated Elixir protobuf code (agent.pb.ex) with LLDP_TOPOLOGY job type
- Added validator for LLDP topology results with neighbor and management address validation
- Added AgentChannel handler for "lldp_topology_result" events
- Added Topology.upsert_neighbor/3 public API for storing agent-discovered neighbors
- Stores LLDP neighbors in device_neighbors table via validated protobuf messages

Agent changes were committed separately in towerops-agent repo (commit b6f60c8).

Files modified:
- priv/proto/agent.proto: Added LLDP message definitions
- lib/towerops/proto/agent.pb.ex: Regenerated from proto file
- lib/towerops/agent/validator.ex: Added validate_lldp_topology_result/1
- lib/towerops_web/channels/agent_channel.ex: Added lldp_topology_result handler
- lib/towerops/topology.ex: Added upsert_neighbor/3 public wrapper
2026-03-05 11:16:28 -06:00
7d73193dc6
docs: update changelog for LLDP topology discovery 2026-03-05 10:54:53 -06:00
e13eb3bbce
feat: implement LLDP topology discovery via SNMP
Implements Phase 1 of network topology discovery using LLDP (Link Layer
Discovery Protocol) via SNMP, inspired by concepts from lldp2map.

New Features:
- LLDP-MIB walker for discovering network neighbors via SNMP
- Database schema for storing neighbor relationships
- Functions to discover, store, and query device neighbors
- Automatic device linking when neighbors are found in database

Components Added:
- Migration: device_neighbors table with unique constraint on
  (device_id, local_port, neighbor_name)
- Schema: Towerops.Topology.DeviceNeighbor for neighbor relationships
- Module: Towerops.Topology.Lldp for SNMP LLDP-MIB walking
- Functions: discover_lldp_neighbors/1, list_lldp_neighbors/1,
  remove_stale_lldp_neighbors/1 in Topology context

LLDP-MIB OIDs:
- 1.0.8802.1.1.2.1.3.3.0 (lldpLocSysName)
- 1.0.8802.1.1.2.1.3.7.1.4 (lldpLocPortDesc)
- 1.0.8802.1.1.2.1.4.1.1.7 (lldpRemPortId)
- 1.0.8802.1.1.2.1.4.1.1.8 (lldpRemPortDesc)
- 1.0.8802.1.1.2.1.4.1.1.9 (lldpRemSysName)
- 1.0.8802.1.1.2.1.4.2.1.3 (lldpRemManAddr)

CI Fix:
- Updated config/test.exs to use DATABASE_URL from environment in CI
- Fixes "connection refused to localhost:5432" errors in CI builds
- Falls back to localhost config for local development

Code Quality:
- Refactored LLDP module to reduce cyclomatic complexity
- Extracted helper functions to improve readability
- Used 'with' statement to reduce nesting in parse_management_address
- All Credo checks passing

Next Steps (Phase 2):
- Background worker for automated topology discovery
- Topology visualization in LiveView
- Recursive discovery from seed devices
2026-03-05 10:53:57 -06:00
29b23fb06c
docs: update changelogs for reliability audit completion
- Added entries for organization_id alert optimization
- Added entries for cascade delete safety improvements
- Added entry for sensor state validation
- Updated user-facing changelog with performance improvements
- All 23 bugs from reliability audit now addressed
2026-03-05 09:34:57 -06:00
31cccf97b9
perf: add organization_id to alerts for fast single-table queries
- Denormalize organization_id from device to alerts table
- Add composite indexes on (organization_id, alert_type, resolved_at)
- Optimize list_organization_active_alerts to use single-table query
- Eliminates expensive 3-table joins (alerts → device → site)
- Auto-populate organization_id when creating alerts
- Backfill existing alerts in migration

Fixes bug #14 from reliability audit.
2026-03-05 09:32:43 -06:00
a230f1bfb5
fix: prevent silent data loss from cascade deletes on agent tokens
Changed foreign key constraints from CASCADE to RESTRICT for:
- agent_tokens.organization_id
- agent_assignments.agent_token_id

**Problem:**
Deleting an organization would cascade delete all agent tokens, which
would then cascade delete all agent assignments, causing silent data
loss without any audit trail.

**Solution:**
Use RESTRICT instead of CASCADE - this prevents deletion if there are
dependent records, forcing explicit cleanup and preventing accidental
data loss.

**Impact:**
- Organizations cannot be deleted if they have agent tokens
- Agent tokens cannot be deleted if they have active assignments
- User must explicitly clean up dependencies first

This provides better data integrity and prevents accidental data loss.

Fixes Medium Bug #15 from reliability audit.
2026-03-05 09:25:55 -06:00