- NATS request-reply on device.discover.snmp with queue group discover-workers
- Probes sysObjectID.0, sysDescr.0, sysName.0 via SNMP GET
- Credentials from request payload (not database), never logged
- 5-second timeout on both connect and GET operations
- Supports SNMP v1, v2c, and v3 with all auth/priv protocols
- Builds gosnmp client inline to avoid snmp->bus import cycle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
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>
- 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>
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>
- 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>
- 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>
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>
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>
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>