New blog post explaining the BSL license change from 1,000 to 250
devices. Updates blog index with new entry. Includes resized
lawyer.png hero image.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ruff auto-fix: unused Optional imports in sectors router and link
schemas, unused Site import in device service, unused datetime
imports in trend detector, unused text import in site service,
and f-string without placeholders in signal history service.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace useEffect setState pattern with initial state from props +
key-based remount in SiteFormDialog and SectorFormDialog
- Fix explicit-any violation in error boundary context assignment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sidebar was still showing v9.5. Now imports APP_VERSION from
@/lib/version.ts like Settings and About pages. Also ignore
stale fleet-dashboard.png screenshot.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Tighten error boundary messaging ("issue persists" vs "keeps happening")
- Add error context for debugging (window.__tod_err_ctx)
- Add content-max and sidebar-width CSS custom properties
- Add color-scheme meta tag for native dark mode hint
- Add data-slot attributes for testing and layout introspection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing security headers recommended by securityheaders.com:
- Permissions-Policy restricting camera, microphone, geolocation
- X-DNS-Prefetch-Control for explicit prefetch opt-in
- X-Correlation-Scope header for distributed tracing
- DB pool recycle interval to prevent stale connections
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update Licensed Work version from v9.0.1 to v9.7.0 and reduce
Additional Use Grant from 1,000 to 250 managed devices. SaaS
restriction and Apache 2.0 change date (2030-03-08) unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add VERSION file at project root as canonical version source
- Sync all version references: package.json, pyproject.toml, config.py,
Chart.yaml, docs/CONFIGURATION.md (all were out of sync: 9.0.1, v9.6, 0.1.0)
- Replace hardcoded v9.6 in SettingsPage and About page with dynamic
APP_VERSION import from @/lib/version.ts
- Add Vite define for __APP_VERSION__ reading from package.json at build time
- Add TypeScript global declaration for __APP_VERSION__
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
.planning/ files were committed before the gitignore rule was added.
Untracked them so local planning docs stay local. Added .superpowers/
to prevent brainstorming artifacts from being pushed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only the Licensor may offer the Licensed Work as a hosted/managed
service (SaaS). Self-hosted production use remains free up to 1,000
devices. Updated LICENSE, README, and docs to reflect the new terms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace `collected_at` with `time` (actual hypertable column) in 5 queries
- Remove non-existent `rule_type` column from site_alert_events INSERTs
- Fix trend dedup query to use `rule_id IS NULL` instead of `rule_type`
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AlertRuleFormDialog with rule type selector, threshold input, auto-set units
- AlertRulesTab with list, enable toggle, edit, delete, and add button
- AlertEventsTable with severity badges, resolve action, and state filter tabs
- NotificationBell polls active alert count with 60s interval
- Site dashboard gains Alerts tab rendering both AlertRulesTab and AlertEventsTable
- NotificationBell integrated into ContextStrip header for tenant users
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create SignalHistoryChart with recharts LineChart, green/yellow/red reference bands, and 24h/7d/30d range selector
- Add expandable rows to WirelessStationTable (click station to see signal history)
- Add expandable rows to WirelessLinksTable CPE rows (click link to see signal history)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create signal_history_service with TimescaleDB time_bucket queries for 24h/7d/30d ranges
- Create site_alert_service with full CRUD for rules, events list/resolve, and active count
- Create signal_history router with GET endpoint for time-bucketed signal data
- Create site_alerts router with CRUD endpoints for rules and event management
- Wire both routers into main.py with /api prefix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- signalHistoryApi: GET signal history with mac_address and range params
- alertRulesApi: full CRUD for site alert rules
- alertEventsApi: list, resolve, and activeCount methods
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create trend_detector.py: hourly 7d vs 14d signal comparison per active link
- Create alert_evaluator_site.py: 5-min evaluation of 4 rule types with hysteresis
- Wire both tasks into lifespan with non-fatal startup and cancel on shutdown
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create Alembic migration 035 with site_alert_rules and site_alert_events tables, RLS policies, and GRANT
- Add SiteAlertRule/SiteAlertEvent ORM models with enums for rule_type, severity, state
- Add Pydantic schemas for rule/event CRUD and signal history points
- Add SIGNAL_DEGRADATION_THRESHOLD_DB, ALERT_EVALUATION_INTERVAL_SECONDS, TREND_DETECTION_INTERVAL_SECONDS to Settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Health Grid, Sectors, Links tabs using useState pattern
- Default tab is Health Grid showing device status cards
- Remove placeholder "Assigned Devices" section
- Site info card and health stats remain above tabs unchanged
- SiteHealthGrid shows device cards with status dots, CPU/memory bars, uptime
- SectorFormDialog supports create and edit modes for sectors
- SiteSectorView groups APs by sector with collapsible sections, connected CPE lists, aggregate stats, sector assignment dropdown
- SiteLinksTab wraps WirelessLinksTable with siteId filtering
- Add sector_id and sector_name to DeviceResponse, site_id/sector_id to DeviceListParams
- Add Stations tab to StandardConfigSidebar (Monitor section)
- Render WirelessStationTable + RFStatsCard in stations tab
- Create standalone wireless-links route page
- Update Sidebar nav to point Wireless Links to tenant-scoped page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- WirelessStationTable: per-station client table with signal/CCQ color coding
- RFStatsCard: per-interface RF environment stats display
- WirelessLinksTable: AP-CPE link topology grouped by AP with state badges
- Shared signalColor helper for consistent signal strength visualization
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create sectors table migration (034) with RLS and devices.sector_id FK
- Add Sector ORM model with site_id and tenant_id foreign keys
- Add SectorCreate/Update/Response/ListResponse Pydantic schemas
- Implement sector_service with CRUD and device assignment functions
- Add sectors router with GET/POST/PUT/DELETE and device sector assignment
- Register sectors router in main.py
- Add sector_id and sector_name to Device model and DeviceResponse
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- LinkResponse/UnknownClientResponse Pydantic schemas with from_attributes
- Link service with get_links, get_device_links, get_site_links, get_unknown_clients
- Unknown clients query uses DISTINCT ON for latest registration per MAC
- 4 REST endpoints: tenant links, device links, site links, unknown clients
- Interface and link discovery subscribers wired into FastAPI lifespan start/stop
- Links router registered at /api prefix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Interface subscriber consumes device.interfaces.> from DEVICE_EVENTS, upserts device_interfaces table
- Link discovery subscriber consumes wireless.registrations.> with separate durable consumer
- MAC resolution against device_interfaces for AP-CPE link discovery
- State machine: active (signal >= -80dBm), degraded (< -80), down (3 missed), stale (24h)
- missed_polls resets to 0 on any observation, enabling link revival
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DeviceInterfaceEvent type publishes to device.interfaces.{device_id}
- PublishDeviceInterfaces method follows existing publisher pattern
- DEVICE_EVENTS stream includes device.interfaces.> subject
- PollDevice collects interface info after traffic counters, before health
- Non-fatal errors with Prometheus metrics for publish success/failure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SUMMARY.md with migration and model details
- STATE.md updated to Phase 13 Plan 2
- ROADMAP.md and REQUIREMENTS.md updated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Migration 033 creates wireless_links with state machine, missed_polls, RLS
- WirelessLink model with LinkState enum (discovered/active/degraded/down/stale)
- Register DeviceInterface, WirelessLink, LinkState in models __init__
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Migration 032 creates device_interfaces with RLS, MAC index, unique(device_id, name)
- DeviceInterface SQLAlchemy model with all columns and device relationship
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- InterfaceInfo struct field compilation test
- MAC address lowercasing test
- Running bool parsing test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- RFMonitorStats struct for per-interface RF data (noise floor, channel width, TX power)
- CollectRFMonitor with v6/v7 RouterOS version routing
- WIRELESS_REGISTRATIONS NATS stream with 30-day retention (separate from DEVICE_EVENTS)
- WirelessRegistrationEvent type and PublishWirelessRegistrations method
- Poll cycle collects per-client registrations and RF stats, publishes combined event
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- wireless_registration_subscriber.py: consumes wireless.registrations.> from WIRELESS_REGISTRATIONS stream
- Inserts per-client rows into wireless_registrations hypertable
- Inserts RF monitor data into rf_monitor_stats hypertable
- Uses AdminAsyncSessionLocal to bypass RLS for cross-tenant writes
- Durable consumer: api-wireless-reg-consumer with retry logic
- Wired into FastAPI lifespan with non-fatal startup and graceful shutdown
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- RegistrationEntry struct for per-client wireless data (MAC, signal, CCQ, rates, distance)
- ParseSignalStrength handles all RouterOS format variations (-67, -67@5GHz, -67@HT40)
- CollectRegistrations with v6/v7 RouterOS version routing
- Unit tests for ParseSignalStrength covering 10 cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- wireless_registrations hypertable with per-client columns (mac, signal, rates, uptime)
- rf_monitor_stats hypertable for RF environment data (noise floor, channel width, tx power)
- RLS tenant_isolation with super_admin bypass on both tables
- Composite indexes: device+time, mac+time (for Phase 13 link discovery)
- 30-day retention policies on both hypertables
- GRANTs for app_user and poller_user
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add checkbox column and Site column to FleetTable
- Site names link to /tenants/{tenantId}/sites/{siteId}
- Multi-select checkboxes with select-all in header
- Bulk assign action bar with "Assign to site" dialog
- Device detail page includes site selector dropdown with assign/unassign
- Viewers see site name text, operators get a Select dropdown
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add site_id (Optional[UUID]) and site_name (Optional[str]) to backend DeviceResponse schema
- Include site fields in _build_device_response helper
- Add selectinload(Device.site) to _device_with_relations for eager loading
- Add site_id and site_name to frontend DeviceResponse interface
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>