- Run ruff format on setup.py to fix pre-existing style violations
- Add CredentialProfile import to models/__init__.py so SQLAlchemy
can resolve the Device.credential_profile relationship in tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Setup.py now asks whether to pull pre-built images from GHCR
(recommended) or build from source. Pre-built mode skips the
15-minute compile step entirely.
- Add .github/workflows/release.yml (builds+pushes 4 images on tag)
- Add docker-compose.build.yml (source-build overlay)
- Switch docker-compose.prod.yml from build: to image: refs
- Add --build-mode CLI arg and wizard step to setup.py
- Bump version to 9.8.1 across all files
- Document TOD_VERSION env var in CONFIGURATION.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Go: nil-safe profile cache in SNMPCollector, updated test assertion
- ESLint: fix conditional useQuery hook in SNMPMetricsSection
- ESLint: remove unused CREDENTIAL_TYPE_LABELS, ChevronDown/Right,
EmptyState import, advancedOpen state
- TypeScript: replace empty interface with type alias
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v9.8 release post covering SNMP support, UI simplification,
limitations, and where the project is heading.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MikroTik is the product. SNMP is a feature. Restored firmware management
and SRP-6a to feature list. SNMP added as one line, not a replacement.
Titles stay MikroTik-first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updated title, meta descriptions, OG/Twitter cards, structured data,
tagline, and feature list to reflect multi-vendor SNMP monitoring.
MikroTik remains prominent — SNMP is positioned alongside it.
Version bumped to 9.8.0 in structured data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused snmpDiscoverResult state and ProfileTestResponse import
- Wrap handleDeviceClick in useCallback, add to Enter shortcut deps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These were tracked before their .gitignore patterns were added.
Removes them from the index without deleting local copies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SNMP devices added without a profile (e.g., via simplified add flow)
were failing with "no SNMP profile assigned". Now falls back to the
generic-snmp profile which collects standard MIB-II metrics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
- 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>
- 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>
- 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>