Commit Graph

509 Commits

Author SHA1 Message Date
Jason Staack
727598cc78 test(18-04): add failing tests for DiscoveryResponder
- Subscribe/unsubscribe lifecycle
- Invalid JSON returns error response
- Missing ip_address returns descriptive error
- Response JSON field names match spec
- Invalid SNMP version rejected
- Default port 161 when zero

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:25:23 -05:00
Jason Staack
8d3952e92d feat(18-02): implement ProfileCache with JSONB compilation and sysObjectID matching
- compileProfileData parses JSONB profile_data into typed CompiledProfile structs
- ProfileCache.Get provides O(1) lookup by profile UUID
- MatchSysObjectID uses longest-prefix-first matching with generic-snmp fallback
- StartRefresh runs background goroutine with configurable interval (default 5min)
- Load atomically replaces in-memory cache under write lock
- counter.go created as blocking prerequisite (Rule 3 deviation from 18-01)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:21:39 -05:00
Jason Staack
0697563a13 feat(18-01): add counter cache with Redis delta computation and SNMPMetricsEvent
- Implement computeCounterDelta for Counter32/Counter64 with wraparound handling
- Sanity threshold discards deltas > 90% of max value (device reset detection)
- CounterCache uses Redis MGET/MSET pipelining for efficient state persistence
- Counter keys use "snmp:counter:{device_id}:{oid}" format with 600s TTL
- Add SNMPMetricsEvent and SNMPMetricEntry structs to bus package
- Add PublishSNMPMetrics publishing to "device.metrics.snmp_custom.{device_id}"
- Full test coverage: 10 counter tests including miniredis integration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:21:25 -05:00
Jason Staack
cec0a8c6d4 test(18-02): add failing tests for ProfileCache and compileProfileData
- 11 test cases covering JSONB compilation, prefix matching, fallback
- Tests reference compileProfileData, ProfileCache, sysOIDEntry (not yet implemented)
- types.go created with CompiledProfile, PollGroup, ScalarOID, TableOID, ColumnOID

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:18:50 -05:00
Jason Staack
9458dadc90 feat(18-01): add gosnmp dependency, SNMP types, and client builder
- Install gosnmp v1.43.2 as direct dependency
- Create snmp package with SNMPConfig, CompiledProfile, PollGroup types
- Implement BuildSNMPClient for v1, v2c, and v3 (all security levels)
- Map auth protocols (MD5, SHA, SHA224-512) and priv protocols (DES, AES128-256)
- MaxRepetitions set to 10 (not gosnmp default 50) for embedded devices
- Full test coverage: 9 tests covering all SNMP versions and protocol mappings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:18:37 -05:00
Jason Staack
a231b18d69 feat(17-03): bulk add endpoint and service with credential profile support
- POST /tenants/{tenant_id}/devices/bulk endpoint with rate limiting
- bulk_add_with_profile service validates profile ownership and type compatibility
- Duplicate IP check prevents adding same IP twice in one tenant
- TCP reachability check for RouterOS devices, skipped for SNMP (UDP)
- Per-device result reporting with partial success support
- Device model updated with device_type, snmp_port, snmp_version, snmp_profile_id columns
- Audit logging for bulk add operations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:59:24 -05:00
Jason Staack
998fff7d01 feat(17-03): add bulk add schemas for credential profile support
- BulkAddWithProfileRequest with credential_profile_id, device_type, defaults
- BulkAddDeviceEntry with IP address validation
- BulkAddDefaults for type-appropriate port/TLS defaults
- BulkAddDeviceResult and BulkAddWithProfileResult for per-device reporting
- Existing BulkAddRequest preserved for backward compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:57:50 -05:00
Jason Staack
7354708df2 feat(17-01): add credential profile service, router, device assignment
- Service with CRUD + Transit encryption for all new credential writes
- Router with 6 endpoints under /tenants/{tenant_id}/credential-profiles
- Delete returns HTTP 409 with device_count when devices reference profile
- Registered credential_profiles_router in main.py
- DeviceUpdate schema accepts optional credential_profile_id
- update_device validates profile belongs to tenant before assigning

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:54:02 -05:00
Jason Staack
eb3ea0def3 feat(17-02): SNMP profile CRUD API and SNMP metrics query endpoint
- Add Pydantic schemas for SNMP profile CRUD (list excludes profile_data JSONB)
- Add 5-route SNMP profiles router with system profile protection (403)
- Add device deletion protection for referenced profiles (409)
- Add time-bucketed SNMP metrics query endpoint with metric_name/group filters
- Add distinct metric names endpoint for frontend dropdowns
- Register snmp_profiles_router in main.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:52:58 -05:00
Jason Staack
3d149b674f feat(17-01): add CredentialProfile model and Pydantic schemas
- SQLAlchemy model mapping to credential_profiles table (migration 037)
- CredentialProfileCreate with model_validator enforcing per-type required fields
- CredentialProfileUpdate with conditional validation on type change
- CredentialProfileResponse without any credential fields (write-only)
- Device model updated with credential_profile_id FK and relationship

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:51:53 -05:00
Jason Staack
390df0531d feat(17-02): add snmp_custom handler and NAK safety net to metrics subscriber
- Add _insert_snmp_custom_metrics handler for custom SNMP OID events
- Insert all 9 columns into snmp_metrics hypertable
- Change unknown metric types from ACK to NAK for redelivery safety
- Prevents permanent data loss during deployment ordering mismatches

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:51:02 -05:00
Jason Staack
ad75a19f5d feat(16-04): update Scheduler to dispatch by device_type via collectors
- Add collectors map[string]Collector field to Scheduler struct
- Register RouterOSCollector for "routeros" inside NewScheduler
- Replace direct PollDevice call with collector dispatch by dev.DeviceType
- Default empty DeviceType to "routeros" for backward compatibility
- Log error and exit device loop for unknown device types
- Circuit breaker logic unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:34:27 -05:00
Jason Staack
1b89b122ee feat(16-04): add Collector interface and RouterOSCollector
- Define Collector interface with Collect(ctx, dev, pub) error signature
- Implement RouterOSCollector as thin wrapper delegating to PollDevice
- Add compile-time interface assertion for RouterOSCollector

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:32:58 -05:00
Jason Staack
89d904505d feat(16-03): add GetRawCredentials with 4-source fallback, wrap GetCredentials
- GetRawCredentials resolves credentials: device transit, device legacy, profile transit, profile legacy
- Cache key includes source (device/profile) to prevent cross-source poisoning
- GetCredentials is now a backward-compatible wrapper calling GetRawCredentials + ParseRouterOSCredentials
- Add DecryptRaw to device package for raw byte decryption without JSON parsing
- Invalidate clears both parsed and raw cache entries
- All existing callers (PollDevice, CmdResponder, TunnelResponder, BackupResponder, SSHRelay) unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:28:56 -05:00
Jason Staack
b3dbd1e6b9 feat(16-03): add credential type parsers for RouterOS and SNMP
- SNMPCredential struct with v1/v2c/v3 field support
- ParseRouterOSCredentials handles typed and legacy no-type-field JSON
- ParseSNMPCredentials handles snmp_v1, snmp_v2c, snmp_v3 types
- credentialEnvelope for type-agnostic type field peeking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:27:09 -05:00
Jason Staack
d3084abbb9 test(16-02): verify new Device fields in integration test
- Assert DeviceType defaults to "routeros" via COALESCE
- Assert SNMPPort defaults to 161 via COALESCE
- Assert SNMPVersion, SNMPProfileID, CredentialProfileID are nil for
  existing RouterOS devices without profile links
- Assert ProfileEncryptedCredentials and ProfileEncryptedCredentialsTransit
  are nil when no credential profile is linked
- Update test schema with device_type, snmp_port, snmp_version,
  snmp_profile_id, credential_profile_id columns
- Add credential_profiles table to test schema for LEFT JOIN

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:26:53 -05:00
Jason Staack
c1eb9ca41a feat(16-02): extend Device struct and queries for SNMP and credential profiles
- Add 7 new fields to store.Device: DeviceType, SNMPPort, SNMPVersion,
  SNMPProfileID, CredentialProfileID, ProfileEncryptedCredentials,
  ProfileEncryptedCredentialsTransit
- Update FetchDevices query with LEFT JOIN credential_profiles and
  expanded WHERE clause (credential_profile_id IS NOT NULL)
- Update GetDevice query with same JOIN and new columns
- COALESCE defaults: device_type='routeros', snmp_port=161

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:26:03 -05:00
Jason Staack
70c3d8ac7a test(16-03): add failing tests for credential type parsers
- ParseRouterOSCredentials: typed, legacy no-type-field, SNMP rejection, edge cases
- ParseSNMPCredentials: v1, v2c, v3 auth_priv, RouterOS rejection, legacy rejection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:25:59 -05:00
Jason Staack
37ed0242f4 feat(16-01): add devices SNMP columns and snmp_metrics hypertable
- devices table: device_type (default 'routeros'), snmp_port (default 161),
  snmp_version, snmp_profile_id FK -> snmp_profiles, credential_profile_id
  FK -> credential_profiles, with lock_timeout = 3s for safe ALTER
- snmp_metrics: hypertable with 90-day retention, composite index on
  (device_id, metric_name, time DESC), RLS with tenant isolation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:22:27 -05:00
Jason Staack
ad26335300 feat(16-01): add credential_profiles and snmp_profiles tables
- credential_profiles: UUID PK, tenant_id FK with CASCADE, credential_type,
  encrypted credential fields, unique(tenant_id, name), RLS, poller_user GRANT
- snmp_profiles: UUID PK, nullable tenant_id for system profiles, profile_data
  JSONB, partial unique indexes for tenant vs system name uniqueness, RLS with
  system profile visibility to all tenants, poller_user GRANT
- 6 system seed profiles: generic-snmp, network-switch, network-router,
  wireless-ap, ups-device, mikrotik-snmp with full OID collection definitions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:21:22 -05:00
Jason Staack
9c5cf552d9 fix(ui): SSH terminal expanded mode respects sidebar width
Expanded SSH now uses left: var(--sidebar-width) instead of inset-4,
so it fills the content area without covering the sidebar or header.
Styled header/buttons to match Warm Precision.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:42:44 -05:00
Jason Staack
55266a9188 fix(setup): add sslmode=disable to poller DB URL, add --no-https flag
Go Postgres driver defaults to requiring TLS. Container-to-container
Postgres doesn't have TLS configured. Without sslmode=disable the
poller crashes in a restart loop on fresh installs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:38:51 -05:00
Jason Staack
cb2a311a1f feat(setup): add --no-https flag, ask about HTTPS during domain setup
The wizard previously hardcoded https:// for APP_BASE_URL and
CORS_ORIGINS. LAN and dev deployments without TLS need http:// or
browsers silently drop Secure cookies, causing login to fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:30:34 -05:00
Jason Staack
ddb4ce2512 docs: update documentation for v9.7.2 — setup CLI, navigation, UI scale
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:19:21 -05:00
Jason Staack
958571c26a feat(setup): add CLI switches for non-interactive setup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:12:15 -05:00
Jason Staack
dbc8c45914 fix(lint): remove unused imports and variables from redesign
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:05:31 -05:00
Jason Staack
106aa0f708 fix(test): update login test to expect MikroTik instead of MSP
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:53:46 -05:00
Jason Staack
6e874505eb chore: bump version to 9.7.2 · plain
Warm Precision UI redesign, task-based navigation, interaction system,
website and docs restyled. 30+ commits on warm-precision-redesign
branch, merged to main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:50:29 -05:00
Jason Staack
1a7c790515 fix(blog): dial back image filter — lighter sepia, higher opacity 2026-03-21 15:43:56 -05:00
Jason Staack
cb1de32269 fix(blog): blend lawyer image into warm precision palette with sepia/multiply 2026-03-21 15:41:43 -05:00
Jason Staack
f45236857f fix(website): restore hover/click screenshots, widen setup box, remove hint text 2026-03-21 15:39:48 -05:00
Jason Staack
3b5e2cad01 fix(website): simplify screenshots — plain images with captions, no interactivity 2026-03-21 15:38:15 -05:00
Jason Staack
e8151845ed fix(website): fix dark nav header on blog, add interactive screenshots
- Remove site-nav--dark class from all pages (blog, homepage)
- Add nav color overrides to all blog pages (light background, dark text)
- Homepage: hover screenshots to swap dark/light, click to expand both
  side by side in overlay

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:36:08 -05:00
Jason Staack
5b78cd9869 docs(website): apply Warm Precision styling to all blog pages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:32:59 -05:00
Jason Staack
28035eb5dd docs(website): apply Warm Precision styling to docs page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:29:38 -05:00
Jason Staack
18a176c89a fix(website): fix code block wrapping in setup section 2026-03-21 15:29:02 -05:00
Jason Staack
ce894505dc fix(website): show both light and dark mode screenshots side by side 2026-03-21 15:27:00 -05:00
Jason Staack
c13d7f0f7c docs(website): rewrite homepage per Warm Precision redesign direction
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:23:07 -05:00
Jason Staack
a3b49620e7 feat: Warm Precision UI redesign — new design system, task-based navigation, interaction system
30 commits delivering:
- Warm Precision token system (warm olive/stone palette, no blue)
- Task-based sidebar (Operate/Act replacing Fleet/Config/Admin)
- ContextStrip removed, features moved to sidebar
- Device workspace header with breadcrumb and metadata
- 50ms interaction system (hover/focus/active/press states)
- Skeleton loaders replaced with honest loading states
- Needs Attention dashboard panel with device links
- Joined metrics strip
- UI scale selector (100%/110%/125%)
- All hardcoded colors tokenized
- 143 files changed, net -34 lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:10:37 -05:00
Jason Staack
43309e19ca fix(ui): final cleanup — signal chart hex, checkbox/select tokens
- SignalHistoryChart: #3b82f6 → hsl(var(--accent))
- Checkbox: opacity-50 disabled → text-muted/border-subtle, proper
  focus ring, bg-panel surface, checked text-background
- Select: opacity-50 disabled → text-muted/border-subtle, proper
  focus ring, radius-control, bg-panel, text-xs

Zero old token names, zero blue hex, zero opacity-disabled remaining.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:04:43 -05:00
Jason Staack
04af5536c2 feat(ui): add UI scale selector (100% / 110% / 125%)
Three-level zoom control in sidebar footer. Uses CSS zoom property,
persisted to localStorage via Zustand store. Applied on mount via
AppLayout useEffect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:57:04 -05:00
Jason Staack
a3b5fd1848 feat(ui): Needs Attention items link to device, show map icon
- Hostname is now a Link to the device detail page
- MapPin icon shown for devices with coordinates, links to /map
- Hover accent color on both links
- Also fixes tenant-switch query bug and VPN tab colors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:49:20 -05:00
Jason Staack
5c9915d175 fix(ui): Needs Attention updates on tenant switch, VPN colors tokenized
- Dashboard fleet query now uses selected tenant ID for super_admin
  instead of always fetching all tenants. Needs Attention, metrics
  strip, and all widgets update when switching tenants.
- VPN tab: replace hardcoded purple/blue/green hex with token colors
- Add Certificates and VPN back to sidebar low-frequency section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:42:49 -05:00
Jason Staack
2da9fe0373 feat(ui): add Certificates and VPN back to sidebar low-frequency section
These were removed during the Operate/Act restructure but are
standalone management pages that need direct nav access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:41:11 -05:00
Jason Staack
4c3b95857a feat(ui): add Needs Attention panel and metrics strip to dashboard
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:30:33 -05:00
Jason Staack
86cff80c97 fix(ui): replace hardcoded interface type colors and diff viewer colors
InterfacesPanel: replace Tailwind palette hex (#3B82F6 blue, #8B5CF6
purple, etc.) with token references (accent, info, warning, success,
error). No more blue or purple interface badges.

DiffViewer: replace raw blue/green/red Tailwind classes with token
classes (info, success, error).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:00:03 -05:00
Jason Staack
0313909d93 fix(ui): scroll to top when switching device sidebar tabs
Wraps setActiveTab to also scroll #main-content to top. Prevents
stale scroll position when navigating from a long tab (e.g. Firewall)
to a short one (e.g. SNMP).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:54:09 -05:00
Jason Staack
91eea99aca chore(ui): Stage 4 cleanup — delete ContextStrip, fix transition-all,
replace chart hex colors

- Delete ContextStrip.tsx (no longer imported)
- Sidebar: transition-all → transition-[width]
- Charts: replace #38BDF8/#94a3b8/#334155 with token references
- EmergencyKitDialog hex preserved (print template)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:46:09 -05:00
Jason Staack
b8d8abde32 fix(ui): replace hardcoded chart hex colors with Warm Precision tokens
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:45:52 -05:00
Jason Staack
72c09d95bb feat(ui): add error/empty state classes, update toast styling
- Toast: bg-elevated surface, border-default, radius-control, remove
  richColors (use token colors instead of Sonner defaults)
- Add .panel-empty and .panel-error CSS utility classes
- Available for Phase 4 page-level refinement

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:43:56 -05:00