Commit Graph

498 Commits

Author SHA1 Message Date
Jason Staack
f47438a5a5 feat(ui): add delete button to fleet table rows
Trash icon appears on row hover. Confirms before deleting.
Both virtual and non-virtual row renderers have group/row
class for the hover-to-show pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 08:21:51 -05:00
Jason Staack
1cb869a195 feat(api): add device_type filter to device list endpoint
Operators can now filter the device list by device_type (routeros, snmp)
via ?device_type=snmp query parameter. Enables the frontend type filter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 08:16:57 -05:00
Jason Staack
36c0e25aea fix(model): remove ORM FK for snmp_profile_id to avoid NoReferencedTableError
SQLAlchemy couldn't resolve ForeignKey("snmp_profiles.id") because
there's no SNMPProfile ORM model — profiles are managed via raw SQL.
The FK constraint exists at the DB level via migration 039. The ORM
column is now a plain UUID.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 08:11:03 -05:00
Jason Staack
d2f48552bd fix(ui): handle unwrapped credential profile arrays in AddDevice and BulkAdd
The list API now returns a flat array but AddDeviceForm and BulkAddForm
still accessed .profiles on the result. Now handles both shapes with
Array.isArray fallback for safety.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 08:04:44 -05:00
Jason Staack
1888f850af fix(migration): grant app_user permissions on SNMP tables
The app_user role had no INSERT/UPDATE/DELETE on credential_profiles,
snmp_profiles, or snmp_metrics — causing 'permission denied' when
creating credential profiles or SNMP profiles from the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 07:59:35 -05:00
Jason Staack
7dc941e467 fix(ui): fix credential profile field names and list response unwrapping
- credentialProfilesApi.list now unwraps {profiles: [...]} wrapper
- CredentialProfilesPage handles both array and wrapped responses
- Renamed privacy_protocol → priv_protocol to match backend schema
- Renamed privacy_passphrase → priv_passphrase to match backend
- Renamed security_name → username (SNMPv3 uses username field)
- Removed security_name from CredentialProfileCreate type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 07:50:35 -05:00
Jason Staack
cffe12bf53 fix(ui): correct SNMP protocol values and simplify add-device flow
- Add missing fields to SNMPProfileResponse (sys_object_id, vendor, category, tenant_id)
- Fix security level values to snake_case (no_auth_no_priv, auth_no_priv, auth_priv) matching backend
- Fix auth protocols to SHA256/SHA384/SHA512 (was MD5/SHA/SHA256)
- Fix privacy protocols to AES128/AES256 (was DES/AES/AES256)
- Apply same protocol fixes to ProfileTestPanel
- Fix BulkAddForm to show both v2c and v3 credential profiles (was hardcoded to v2c)
- Simplify SNMP tab in AddDeviceForm to IP + credential profile + discover-and-add button
- Show guidance link when no SNMP credential profiles exist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:13:06 -05:00
Jason Staack
bdf5b54713 fix(api): add SNMP fields to device create/read/update schemas and service
DeviceCreate now accepts device_type, snmp_port, snmp_version,
snmp_profile_id, credential_profile_id, and community string.
Username/password are optional (not needed for SNMP devices).
A model validator ensures at least one credential method is provided.

DeviceResponse and DeviceUpdate include the same SNMP fields so
list/detail endpoints return them and users can modify them.

The create_device service skips TCP probe for SNMP devices (UDP),
encrypts inline community strings via Transit, and sets all SNMP
columns on the Device ORM object.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:11:10 -05:00
Jason Staack
38f33eb550 fix(ui): unwrap SNMPProfileListResponse wrapper in API client
The list endpoint returns {profiles: [...]} but the client expected
a flat array. Now correctly unwraps r.data.profiles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:56:25 -05:00
Jason Staack
f7aa685a5d refactor(ui): redesign SNMP profile editor for progressive disclosure
The profile editor was dumping everything on one page — MIB upload,
OID tree, poll groups, test panel — overwhelming for anyone who
doesn't live and breathe SNMP. Redesigned with tabbed layout:

- Basics tab: name, category, description (the 95% case)
- OIDs tab: flat table of what's collected, simple manual add
- Advanced tab: MIB upload, OID tree browser, auto-detection
- Test tab: test profile against live device (edit mode only)

Also added Clone action on built-in profiles — the primary way
to create a custom profile. Most users should never need the
Advanced tab at all.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:41:16 -05:00
Jason Staack
cbd8ce1237 fix(docker): place MIB parser in /usr/local/bin to survive COPY backend/
The tod-mib-parser at /app/ was overwritten by COPY backend/ .
Move to /usr/local/bin/ and update config path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:27:52 -05:00
Jason Staack
83f11cc739 fix(snmp): ship MIB parser binary in API container with pre-loaded MIBs
- Build tod-mib-parser in both poller and API Dockerfiles
- Bundle 16 standard MIBs (IF-MIB, HOST-RESOURCES, SNMPv2, etc.)
- Pass --search-path /app/mibs to parser so dependencies resolve
- Users no longer need to upload standard MIBs manually

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:22:55 -05:00
Jason Staack
4c21539e40 fix(ui): add Outlet to settings layout for child route navigation
Settings child routes (credentials, snmp-profiles, api-keys) were not
rendering because the parent settings.tsx had no Outlet. Now detects
child routes and renders Outlet, or shows SettingsPage index.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:03:53 -05:00
Jason Staack
e152c741e5 fix: replace all :: type casts with CAST() in SQLAlchemy text() calls
SQLAlchemy's text() interprets :name::type as two named parameters.
Fixes syntax errors in link discovery, signal history, and SNMP profile
CRUD that caused 500 errors at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:59:20 -05:00
Jason Staack
2fff669cc3 fix(migration): use CAST instead of :: for jsonb type cast in seed data
SQLAlchemy's text() interprets ::jsonb as a named parameter binding.
Use CAST(:profile_data AS jsonb) to avoid the collision.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:27:52 -05:00
Jason Staack
53a858cc16 chore: bump version to 9.8.0
SNMP Device Integration milestone complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:08:06 -05:00
Jason Staack
f6fb206d4d fix(20): add SNMP profiles settings link and device_count to profile list
Adds SNMP Device Profiles card to SettingsPage for discoverability.
Adds device_count correlated subquery to profile list SQL and schema
field so the frontend profile cards show accurate device counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:41:04 -05:00
Jason Staack
2c74783f17 docs(20-03): complete frontend profile editor plan
- SUMMARY.md with task commits and deviation documentation
- STATE.md updated: phase 20 complete, all 19/19 plans done
- ROADMAP.md progress updated for phase 20
- REQUIREMENTS.md: PROF-03, UI-07 marked complete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:36:27 -05:00
Jason Staack
7644e561d7 feat(20-03): add SNMP profile test panel component
- Collapsible test-against-device panel with SNMP v1/v2c/v3 fields
- Conditional v3 fields (security level, auth/priv protocols)
- Test button calls snmpProfilesApi.testProfile
- Success shows green device-info panel (sysName, sysDescr, sysObjectID)
- Failure shows red error panel
- Disabled when profile not yet saved

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:34:09 -05:00
Jason Staack
0429073774 feat(20-03): add virtualized OID tree browser component
- Flatten tree for tanstack/react-virtual virtualization
- Expand/collapse branch nodes with chevron toggle
- Checkbox selection for leaf nodes (selectable OIDs)
- Search filter by name or OID substring
- Expand all / collapse all toolbar buttons
- ARIA tree roles for accessibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:34:00 -05:00
Jason Staack
b5f96b820f feat(20-03): add SNMP profile editor page, route, and API client extensions
- Extend snmpProfilesApi with create, update, delete, parseMib, testProfile
- Add OIDNode, MIBParseResponse, ProfileTestRequest/Response, SNMPProfileCreate types
- Create settings.snmp-profiles route with RBAC and tenant resolution
- Build SNMPProfileEditorPage with list/edit views, MIB upload, poll groups
- Remove pre-existing duplicate credentialProfilesApi declaration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:33:49 -05:00
Jason Staack
da598f79a0 feat(20-01): add tod-mib-parser Go CLI binary
- Add gosmi v1.0.4 dependency for MIB parsing
- Create poller/cmd/mib-parser/ with main.go and tree.go
- CLI accepts MIB file path and optional --search-path
- Outputs JSON OID tree with oid, name, description, type, access, status, children
- Errors output as JSON {"error":"..."} to stdout (exit 0) for Python backend
- Panic recovery wraps gosmi LoadModule for malformed MIBs
- Parent-child tree built from OID hierarchy with numeric sort

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:22:16 -05:00
Jason Staack
655f1eadae feat(20-02): add parse-mib and test-profile API endpoints
- POST /snmp-profiles/parse-mib: upload MIB file, subprocess-call tod-mib-parser, return OID tree JSON
- POST /snmp-profiles/{id}/test: test profile connectivity via NATS discovery probe to poller
- New snmp_proxy service module following routeros_proxy.py lazy NATS pattern
- Pydantic schemas: MIBParseResponse, ProfileTestRequest, ProfileTestResponse, ProfileTestOIDResult
- MIB_PARSER_PATH config setting with /app/tod-mib-parser default
- MIB parse errors return 422, not 500; temp file cleanup in finally block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:21:08 -05:00
Jason Staack
77e9b680ae feat(19-04): add SNMPMetricsSection component for SNMP profile display
- Shows assigned SNMP profile name with system badge indicator
- Shows profile description when available
- Returns null when no snmpProfileId is assigned
- Fetches profile data via snmpProfilesApi.get
- Foundation for Phase 20 custom OID charting (PROF-03)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:05:15 -05:00
Jason Staack
b9f60223fd feat(19-04): make device detail page type-aware with conditional rendering
- Add isRouterOS/isSNMP constants derived from device.device_type
- Guard SimpleModeToggle, SSHTerminal, RollbackAlert, TlsSecurityBadge behind isRouterOS
- Config backup query only fires for RouterOS devices
- SNMP devices get dedicated layout: system info, SNMP profile, interface gauges, groups, tags, alerts
- Header metadata shows SNMP version for SNMP devices, RouterOS version for RouterOS devices

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:05:03 -05:00
Jason Staack
333a985b1e docs(19-02): complete Add Device dialog + Bulk Add plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 20:01:13 -05:00
Jason Staack
caf143532b feat(19-02): create BulkAddForm component for IP list bulk operations
- Credential profile dropdown filtered by device type (routeros/snmp)
- IP textarea parses one-per-line IPv4 addresses with deduplication
- Optional hostname prefix generates numbered names (e.g., tower-ap-01)
- SNMP variant shows SNMP port and device profile selector
- RouterOS variant shows API port and TLS API port fields
- Results display with per-device CheckCircle2/XCircle success/failure icons
- Calls devicesApi.bulkAddWithProfile for backend bulk add endpoint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:59:34 -05:00
Jason Staack
fbad0e9a56 feat(19-01): add device type icon and filter to fleet table
- Add DeviceTypeIcon component (Router for RouterOS, Network for SNMP)
- Add Type column to desktop table between Status and Hostname
- Add type icon to mobile DeviceCard view
- Show em-dash for RouterOS version on SNMP devices
- Add device type filter dropdown to DeviceFilters (All / RouterOS / SNMP)
- Pass device_type through URL search params to API query
- Update colSpan from 11 to 12 for empty/loading states

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:59:29 -05:00
Jason Staack
74ddaad551 feat(19-02): redesign Add Device dialog with RouterOS, SNMP, and VPN tabs
- Always show three-tab layout (RouterOS, SNMP, VPN) instead of conditional two-tab
- RouterOS tab: credential profile toggle (profile mode vs manual credentials)
- SNMP tab: version selector (v2c/v3), credential profile, device profile, port
- Both tabs have "Add Multiple" toggle to switch to BulkAddForm
- VPN tab renders existing VpnOnboardingWizard unchanged
- All form state resets on dialog close

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:59:24 -05:00
Jason Staack
cd50e617e1 feat(19-03): add credentials route and settings page link
- Create settings.credentials.tsx route using dot-notation pattern matching api-keys
- RBAC guard restricts access to tenant_admin and above
- Super admin org selector integration for multi-tenant support
- Add Credential Profiles card to Settings page under Device Credentials section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:59:01 -05:00
Jason Staack
af776ea8ff feat(19-03): add credential profiles page and API types
- Create CredentialProfilesPage component with full CRUD for RouterOS and SNMP profiles
- Add credentialProfilesApi types and client to api.ts (blocking dependency from 19-01)
- Profile list grouped by type with device count, edit, and delete actions
- Create/edit dialog with conditional fields per credential type
- SNMPv3 form shows auth/privacy fields based on security_level selection
- Delete confirmation with 409 error handling for linked devices

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:58:02 -05:00
Jason Staack
6b87b88ce4 feat(19-01): extend API client with SNMP types and methods
- Add device_type, snmp_port, snmp_version, snmp_profile_id, credential_profile_id, board_name to DeviceResponse
- Add device_type filter to DeviceListParams
- Add CredentialProfileResponse, CredentialProfileCreate, CredentialProfileUpdate types
- Add credentialProfilesApi with list/get/create/update/delete/devices methods
- Add SNMPProfileResponse type and snmpProfilesApi with list/get methods
- Add BulkAddWithProfile request/result types and devicesApi.bulkAddWithProfile method
- Add SNMPMetricPoint type and metricsApi.snmp method

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:57:02 -05:00
Jason Staack
cec645a109 fix(18): lift Redis distributed lock into scheduler for all collector types
The SNMP collector was missing the per-device Redis lock that prevents
duplicate polls across pods. Rather than adding the lock to each
collector individually, lift it into runDeviceLoop so ALL collector
types (RouterOS and SNMP) are protected uniformly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:43:06 -05:00
Jason Staack
b2c0dc7a08 feat(18-05): wire SNMPCollector into Scheduler and main.go
- Add RegisterCollector method to Scheduler for external collector registration
- Initialize ProfileCache with 5-min refresh in main.go
- Initialize CounterCache using existing Redis client
- Create and register SNMPCollector as "snmp" in scheduler
- Start DiscoveryResponder for NATS SNMP device auto-detection
- Existing RouterOS polling path completely unaffected

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:34:23 -05:00
Jason Staack
ffd2629ff0 feat(18-05): add SoftwareVersion field to DeviceStatusEvent
- Additive field with omitempty tag for SNMP device identification
- Existing RouterOS events produce identical JSON (field not set)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:32:49 -05:00
Jason Staack
9480ba9e3f feat(18-03): implement SNMPCollector.Collect with profile-driven polling
- SNMPCollector implements poller.Collector interface
- Profile-driven OID collection: scalars via Get, tables via Walk/BulkWalk
- BulkWalk wrapped in withTimeout to prevent indefinite hangs
- SNMPv1 uses Walk, v2c/v3 uses BulkWalk (MaxRepetitions=10)
- Safety valve: walkTable aborts at 10,000 PDUs per walk
- Counter delta computation via CounterCache for rate metrics
- Standard metrics routed to DeviceMetricsEvent (interfaces, health)
- Custom metrics routed to SNMPMetricsEvent (snmp_custom)
- DeviceStatusEvent published with online/offline status
- Each poll group collects independently (partial failure tolerant)
- Credential resolution via GetRawCredentials + ParseSNMPCredentials
- ifXTable Counter64 data supersedes ifTable Counter32 via PreferOver
- ssCpuIdle invert_percent transform for CPU load fallback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:28:28 -05:00
Jason Staack
4c667fd454 feat(18-04): implement DiscoveryResponder for SNMP device probes
- 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>
2026-03-21 19:28:28 -05:00
Jason Staack
590b1c8d4d test(18-03): add failing tests for SNMPCollector interface and edge cases
- Compile-time Collector interface assertion
- Collect returns error with nil SNMPProfileID
- Collect returns error with unknown profile ID
- NewSNMPCollector returns initialized struct

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:26:23 -05:00
Jason Staack
2aaccb77be feat(18-03): add OID result mappers for standard and custom metrics
- mapInterfaceMetrics: ifTable/ifXTable to InterfaceStats with Counter64 preference
- mapHealthMetrics: hrProcessorLoad/ssCpuIdle + hrStorageTable to HealthMetrics
- mapCustomMetrics: generic scalar/table results to SNMPMetricEntry
- mapDeviceStatus: sysDescr/sysUptime to DeviceStatusEvent fields
- pduToUint64, pduToString, extractIndex, formatUptime helpers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 19:25:55 -05:00
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