Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
198 commits
Select commit Hold shift + click to select a range
1520c8d
feat(inventory): modules/inventory sync tab with mapping rules
marcinpsk Mar 31, 2026
c53b0c4
fix tests: update db-fallback tests to match new RQ-only cancellation…
marcinpsk Apr 1, 2026
b2fe27d
fix: updated tests
marcinpsk Apr 1, 2026
a60d601
Apply CR findings: code quality, test, docs, and template fixes
marcinpsk Apr 1, 2026
b04c67a
Apply second batch of CR findings on pr/inventory-core
marcinpsk Apr 1, 2026
ca7af1a
Apply CR findings batch 3: test hardening, PlatformMapping lazy impor…
marcinpsk Apr 1, 2026
cc7cb42
cr: batch 4 — prefetch_related, deterministic export, test improvements
marcinpsk Apr 1, 2026
1a9df23
cr: batch 5 — normalization scoping bug, test fixes
marcinpsk Apr 1, 2026
4a73896
cr: batch 6 — normalization rule caching, test correctness
marcinpsk Apr 1, 2026
a5f3b45
fix: FPC slot int/str comparison, stale docstring, test patch targets
marcinpsk Apr 1, 2026
8776a0d
fix: surface ambiguous platform, preloaded-rules fallback, serial mis…
marcinpsk Apr 1, 2026
5d6d856
fix: canonical librenms_id lookup, None guard, transceiver transparen…
marcinpsk Apr 1, 2026
ab85324
Apply CR batch 10 fixes
marcinpsk Apr 2, 2026
a6aece3
Apply CR batch 11 fixes
marcinpsk Apr 2, 2026
75e1a51
fix: normalize librenms_hardware/os to lowercase, fix convert_speed_t…
marcinpsk Apr 2, 2026
281c835
fix(js): address PR #258 review findings
marcinpsk Apr 2, 2026
881c111
fix: handle unsaved manufacturer in normalization, fix JS modal/fetch…
marcinpsk Apr 2, 2026
aa443be
fix: type annotation, non-positive librenms_id guard, htmx label list…
marcinpsk Apr 2, 2026
0773a2f
fix: use _PLACEHOLDER_VALUES for serial normalization, fix docstring
marcinpsk Apr 2, 2026
ae4c50a
Fix CR batch: device_type ambiguity, platform MOR, cache invalidation…
marcinpsk Apr 2, 2026
1e29fc5
Promote SKIP_TYPES and _NON_HARDWARE_CLASSES to module-level constants
marcinpsk Apr 2, 2026
7e70201
Fix find_matching_platform docstring and non-positive string librenms…
marcinpsk Apr 2, 2026
32562e7
fix: normalize placeholder serials and txr_type in modules_view
marcinpsk Apr 2, 2026
513932b
Fix whitespace-only librenms_id, BUILTIN model placeholder, FPC-scope…
marcinpsk Apr 3, 2026
16ee302
Fix non-integer librenms_id passthrough, stale cache on missing ID, e…
marcinpsk Apr 3, 2026
7dfd436
fix: normalize _GENERIC_CONTAINER_MODELS to lowercase; embed librenms…
marcinpsk Apr 3, 2026
727198d
fix: treat duplicate-serial module conflicts as ambiguous instead of …
marcinpsk Apr 3, 2026
ab8dd7d
fix: clear stale cache on legacy payload; ungate generic-model filter…
marcinpsk Apr 3, 2026
521932e
Remove dead vc_requested assignment left by rebase
marcinpsk Apr 14, 2026
316caa1
fix: VC zero-based detection all-zeros guard; align VC-perm tests wit…
marcinpsk Apr 14, 2026
44836e9
fix: CR batch 12 — serial normalization, conflict disambiguation, mod…
marcinpsk Apr 14, 2026
eceb492
fix: address deferred CR findings from issues #53-#56
marcinpsk Apr 14, 2026
e96b3f3
fix: address 3 remaining CR findings from PR #50
marcinpsk Apr 14, 2026
7464ea5
refactor: drop _extract_inventory_list legacy-list fallback
marcinpsk Apr 15, 2026
ca5f8ef
fix: race conditions, class-aware mappings, stale snapshot in module …
marcinpsk Apr 15, 2026
7a202bd
fix: normalize placeholder serials in UpdateModuleSerialView, fix reg…
marcinpsk Apr 15, 2026
45e8078
fix: lock module row before updating serial in UpdateModuleSerialView
marcinpsk Apr 15, 2026
87400d5
fix: use fullmatch+expand in _find_parent_module_id regex matching
marcinpsk Apr 15, 2026
b56da37
Merge branch 'develop' into pr/inventory-core
bonzo81 Apr 15, 2026
b4a331d
fix: address CR review batch - regex, tests, docs
marcinpsk Apr 15, 2026
d87863f
revert: restore original docs/usage_tips/virtual_chassis.md
marcinpsk Apr 15, 2026
7ac2017
fix: revert bulk_import cancellation, VC comment, VM docstring, sync …
marcinpsk Apr 15, 2026
fbb8d80
fix: restore naming resolution and VC logic lost during rebase
marcinpsk Apr 15, 2026
338ef60
revert: restore original README.md and virtual_chassis.md
marcinpsk Apr 15, 2026
793797a
fix: improve ambiguity test assertion and error message wording
marcinpsk Apr 15, 2026
87772d0
fix: CR review - modal close, test line refs, generic e2e
marcinpsk Apr 17, 2026
be40fd8
Merge branch 'develop' into pr/inventory-core
marcinpsk Apr 17, 2026
631777e
Merge branch 'develop' into pr/inventory-core
marcinpsk Apr 21, 2026
53e76b2
fix: three bugs in module inventory sync reported in PR #261 review
marcinpsk Apr 27, 2026
a1c0be8
fix: improve module table readability in dark mode
marcinpsk Apr 27, 2026
c23a2da
feat: split Mappings into its own navigation group
marcinpsk Apr 27, 2026
c04e700
refactor: reorder navigation groups to Import, Status Check, Mappings…
marcinpsk Apr 27, 2026
0946700
fix: remove table-light from mismatch modal thead for dark mode
marcinpsk Apr 27, 2026
c82fef8
fix: replace table-danger/warning cell backgrounds with text colors i…
marcinpsk Apr 27, 2026
0105892
fix: remove all row background highlighting from module sync table
marcinpsk Apr 27, 2026
04c9b8d
refactor: reorder Mappings group — all mappings first, then Ignore Ru…
marcinpsk Apr 27, 2026
41a7c85
fix: update tests to reflect row_class removal
marcinpsk Apr 27, 2026
3029cd3
fix: auto-generate slug when creating platform from sync page (#279)
marcinpsk Apr 28, 2026
cc40f2a
Revert "fix: auto-generate slug when creating platform from sync page…
marcinpsk Apr 28, 2026
120dd00
fix: generate slug when creating Platform via CreateAndAssignPlatform…
marcinpsk Apr 28, 2026
ab284dc
test: assert Platform constructor receives slug in CreateAndAssignPla…
marcinpsk Apr 28, 2026
4d21c55
fix: generate slug when creating Platform via CreateAndAssignPlatform…
marcinpsk Apr 28, 2026
ff76ea1
test: assert Platform constructor receives slug in CreateAndAssignPla…
marcinpsk Apr 28, 2026
7524a16
fix: wrap OOB row in table to survive HTMX HTML parsing
marcinpsk Apr 28, 2026
5edcaed
fix(imports): add HTTP status codes, remove redundant full_clean, fix…
marcinpsk Apr 28, 2026
385d60c
fix(js): extract JSON error message in delete-interfaces handler; gua…
marcinpsk Apr 29, 2026
3fc09bf
refactor(js): extract fetchErrorMessage() helper to unify fetch error…
marcinpsk Apr 29, 2026
d89852d
feat: exact-name-first platform lookup; optional mapping on platform …
marcinpsk Apr 30, 2026
09ca860
fix(pr#50): address CR feedback on AddDeviceTypeMapping form and plat…
marcinpsk Apr 30, 2026
08d78e1
fix(pr#50): isolate PlatformMapping save; ignore stale autocomplete r…
marcinpsk Apr 30, 2026
cb9f923
fix(pr#50): check add_platformmapping permission when create_mapping …
marcinpsk May 1, 2026
ce74bd1
Merge branch 'main' into pr/inventory-core
marcinpsk May 1, 2026
7880f53
fix: address CodeQL security scan findings
marcinpsk May 4, 2026
3c185f7
revert: remove workflow permissions additions (tracked separately)
marcinpsk May 5, 2026
11d4b32
Fix PR review findings: available_roles, CSRF, test quality
marcinpsk May 5, 2026
cdccd3b
Fix PR #58 review findings (batch 2)
marcinpsk May 5, 2026
334ad0d
Fix PR #58 review findings (batch 3)
marcinpsk May 5, 2026
23d4ad6
Fix #59–#62: YAML anchor, DynamicModelChoiceField, ToggleColumn, writ…
marcinpsk May 5, 2026
7162e99
Merge branch 'bonzo81:master' into main
marcinpsk May 5, 2026
4fe7830
Merge branch 'main' into pr/inventory-core
marcinpsk May 5, 2026
0a3f98f
Fix PR #63 review findings: ordering, error handling, modal title, cl…
marcinpsk May 5, 2026
27412d9
Fix replacement template validation and deterministic bay lookup
marcinpsk May 5, 2026
c253af4
Fix wildcard constraint, ambiguity message, and scoped permissions
marcinpsk May 5, 2026
c878e89
devcontainer: restore full debug output in codespaces-configuration.py
marcinpsk May 5, 2026
c869d81
fix(migration): add preflight dedup before wildcard UniqueConstraint
marcinpsk May 5, 2026
917f504
fix: remove dead check_match, close TOCTOU race, fix migration db alias
marcinpsk May 5, 2026
6409609
fix: guard symmetric delete race in AddDeviceTypeMappingView
marcinpsk May 5, 2026
3bfb51c
fix: catch IntegrityError on concurrent create in AddDeviceTypeMappin…
marcinpsk May 6, 2026
52571a1
fix: inject hx-swap-oob on device row in AddDeviceTypeMappingView res…
marcinpsk May 6, 2026
188b0bc
fix: use int:device_id converter on all device-import URL patterns
marcinpsk May 6, 2026
664f5e9
fix: suppress false-positive CodeQL XSS on AddDeviceTypeMappingView r…
marcinpsk May 7, 2026
6ba8308
fix: use format_html + mark_safe to clear CodeQL XSS on AddDeviceType…
marcinpsk May 7, 2026
b39e07e
fix: skip change-permission escalation in concurrent-create no-op path
marcinpsk May 7, 2026
1cf871d
Merge pull request #282 from marcinpsk/fix/platform-slug
bonzo81 May 7, 2026
96a89e1
docs: clarify mark_safe is a trust assertion, not a sanitizer
marcinpsk May 7, 2026
53cbf3a
feat: add VC-aware module sync
bonzo81 May 7, 2026
e731415
test: align VC module sync expectations
bonzo81 May 7, 2026
216fb84
fix: use all ancestor names as bay-mapping candidates
marcinpsk May 7, 2026
ac7e5c5
Revert "fix: use all ancestor names as bay-mapping candidates"
marcinpsk May 7, 2026
03caca0
test: add prod-shape WS-X4908 bay-matching coverage
marcinpsk May 7, 2026
ef6d269
fix: bail _match_bay_by_position on non-container scaffolding
marcinpsk May 7, 2026
2c839f3
fix: restrict serial_matches_device rule to chassis-level entries
marcinpsk May 7, 2026
d5e493e
fix: class-aware positional fallback + model gap warnings
marcinpsk May 8, 2026
be5f175
feat: ModuleBayMapping suggestions + Add Mapping button on No Bay rows
marcinpsk May 8, 2026
0adcc81
fix: address valid code-review findings
marcinpsk May 8, 2026
873e66d
feat: remove {module} conflict warning, add Generic manufacturer fall…
marcinpsk May 8, 2026
1587c7b
fix: replace {module} conflict tooltip with Name Conflict status badg…
marcinpsk May 8, 2026
74e37df
feat: show conflict reason as tooltip on Name Conflict badge
marcinpsk May 8, 2026
c96be2e
fix: re-apply Name Conflict status after _apply_installed_status
marcinpsk May 8, 2026
d98354c
feat: fix Name Conflict tooltip style and add 'Add Mapping' for No Ty…
marcinpsk May 8, 2026
6cccf1e
fix: remove has_nested_name_conflict — obsolete since NetBox 4.5.5 si…
marcinpsk May 8, 2026
51202ca
Revert "fix: remove has_nested_name_conflict — obsolete since NetBox …
marcinpsk May 8, 2026
1166c40
feat: carrier auto-install rules + vendor-scoped ModuleBayMapping + m…
marcinpsk May 9, 2026
91d81fe
fix: bay-mapping suggestions for integrated children and descr-encode…
marcinpsk May 9, 2026
4c6030b
fix: address CodeRabbit review findings (small fixes)
marcinpsk May 9, 2026
4f8dc88
contrib: vendor-scope generic mapping examples (#73)
marcinpsk May 9, 2026
5219c2c
test/js: minor housekeeping nitpicks (#74)
marcinpsk May 9, 2026
13fc72a
docs: clarify ModuleTypeMapping ambiguity asymmetry (#72)
marcinpsk May 9, 2026
e24bfea
refactor: convert module mismatch modal forms to HTMX (#70)
marcinpsk May 9, 2026
6a3f577
fix(db): tighten uniqueness on nullable manufacturer FKs (#71)
marcinpsk May 10, 2026
7f02cad
Suggest descr-anchored bay mapping for fan/PSU rows whose name is jus…
marcinpsk May 10, 2026
d2df39c
Scope ModuleTypeMapping by manufacturer + fix PlatformMapping ambiguo…
marcinpsk May 10, 2026
1af01cd
Fail-closed Generic ModuleType fallback + CodeRabbit nitpicks
marcinpsk May 10, 2026
fe76d4b
Merge branch 'develop' into pr/inventory-core
marcinpsk May 10, 2026
947ff74
PR #67 4th-pass review fixes (CodeRabbit + Copilot)
marcinpsk May 10, 2026
d0809b4
Add Bay Template QoL: inline modal for missing ModuleBayTemplate
marcinpsk May 11, 2026
9ddd284
Address PR #67 5th-pass review comments
marcinpsk May 11, 2026
4fd7111
Add ModuleBayMapping checkbox to Add-Bay-Template modal
marcinpsk May 11, 2026
384bf55
Offer regex ModuleBayMapping when bay name skeleton matches
marcinpsk May 11, 2026
4a6b0b7
Allow regex bay mapping when libre/NetBox skeletons differ
marcinpsk May 11, 2026
ea3473c
Render bay-mapping preview via DOM nodes, not innerHTML
marcinpsk May 11, 2026
c8d737d
Address PR #67 review pass 6: perms + safe-string fixes
marcinpsk May 11, 2026
1795a8f
Instantiate ModuleBayTemplate onto existing devices/modules
marcinpsk May 11, 2026
44a7961
Squash inventory-core migrations 0010-0015 into a single 0010
marcinpsk May 12, 2026
4a28014
Fix permission gate ordering in SingleModuleVerifyView.post()
marcinpsk May 12, 2026
150a6f4
fix(migrations): lower 0010 external deps to NetBox 4.2 floor
marcinpsk May 13, 2026
c5a5f13
Unify platform-create modal & fix conflict-action error UX
marcinpsk May 16, 2026
2afd7d2
Don't gate librenms_id migration on unrelated validation errors
marcinpsk May 16, 2026
32d52fa
Extend htmx error toast to remaining modal endpoints; update tests
marcinpsk May 16, 2026
56e610b
fix(pr-review): address CodeRabbit feedback batch
marcinpsk May 16, 2026
26771db
fix(pr-review): address 2 new CodeRabbit findings
marcinpsk May 16, 2026
5769bf9
Fix UpdateDevicePlatformView to resolve platform via PlatformMapping
marcinpsk May 16, 2026
846188a
Fix import modal platform row: show Not set + sync for existing devic…
marcinpsk May 16, 2026
01a2f92
fix(pr-review): address 4 CodeRabbit findings
marcinpsk May 16, 2026
c0d17de
fix: format_html no-args TypeError in InventoryIgnoreRuleTable
marcinpsk May 17, 2026
b7968c9
fix(migration): match seeded InventoryIgnoreRules on full signature
marcinpsk May 17, 2026
7eb7926
fix(sync): abort stale verify-module fetches before issuing a new one
marcinpsk May 19, 2026
3d8469b
fix(contrib): anchor the two Nokia transceiver bay regex patterns
marcinpsk May 19, 2026
d5b5575
Fix four CodeRabbit review findings
marcinpsk May 19, 2026
c1f2598
Fix broken tests and PlatformMapping savepoint race condition
marcinpsk May 19, 2026
0118f84
Introduce _parse_request_json helper; guard all json.loads in devices.py
marcinpsk May 19, 2026
0269cb0
Move _parse_request_json to mixins; guard cables_view; fix weak tests
marcinpsk May 19, 2026
e77592f
Rename _parse_request_json -> parse_request_json (make public)
marcinpsk May 19, 2026
6d92824
test: strengthen htmx error-path assertions and align names
marcinpsk May 19, 2026
c9ea0d9
fix: address five CodeRabbit findings from PR #288
marcinpsk May 19, 2026
11e5805
fix(forms): drop legacy poller-group cache fallback + add response-sh…
marcinpsk May 19, 2026
266b650
docs: document module sync, mapping rules and new features from pr/in…
marcinpsk May 17, 2026
8f8ba2d
docs: replace screenshots with dark mode versions, update image refer…
marcinpsk May 17, 2026
c687f81
docs: reorganize mapping screenshots into subdirectories
marcinpsk May 17, 2026
88b4836
docs(mapping_rules): use American spelling consistently
marcinpsk May 19, 2026
0c65cb7
docs(mapping_rules): satisfy MD031/MD040 on fenced code blocks
marcinpsk May 19, 2026
fd99df8
docs(mapping_rules): correct Device Type matching description
marcinpsk May 19, 2026
de47600
docs: correct Platform matching order and ENTITY-MIB scope
marcinpsk May 19, 2026
ec8dbcb
feat: OOB management controller support (Phase 1+2+4a)
marcinpsk May 3, 2026
271c4e9
feat: OOB Phase 3 — AddAsOOBView action + UI button
marcinpsk May 3, 2026
fb8be0b
feat: OOB Phase 4b — combined fetch for modules + cables, _source badges
marcinpsk May 3, 2026
8b8d52f
fix: restore escape() on error messages, fix OOB pattern trailing \b,…
marcinpsk May 15, 2026
ba7dba3
feat(import): inverse-OOB merge UX — promote-to-host action
marcinpsk May 15, 2026
b0ef91e
feat(import): merge two NetBox devices representing same physical box
marcinpsk May 15, 2026
445c0c1
Stage 2b: donor migrated mode — banner + per-row Move actions
marcinpsk May 15, 2026
c65e098
Stage 2b: fix face/rack validation blocking merge
marcinpsk May 15, 2026
8734217
Stage 2b: skip full_clean() in import-conflict actions
marcinpsk May 15, 2026
ace8240
feat(import): auto-populate primary/oob IPs after promote-to-host
marcinpsk May 15, 2026
6f53189
feat(import): centralise IP auto-create + apply to OOB & initial-impo…
marcinpsk May 15, 2026
a63dd0b
Surface IPAM auto-create as toast + tighten promote modal
marcinpsk May 15, 2026
46ee1e2
Stage 4: opt-in IPAM auto-create + DT/Platform mapping shortcuts + pr…
marcinpsk May 15, 2026
83aa510
Add per-action IPAM auto-create toggle on import page + cleanup
marcinpsk May 15, 2026
12a00c2
Recolor OOB badges and surface DT mapping form on unmapped hardware
marcinpsk May 15, 2026
fefac36
Surface 'Auto-created IP' toasts on HTMX import responses
marcinpsk May 15, 2026
9ab8c07
Stage 2/3: clearer merge copy + bulk-import collision detection
marcinpsk May 15, 2026
fd81415
Stage 3: tighten collision modal layout (alert child wrapping)
marcinpsk May 15, 2026
3510944
Fix multi-line {# #} template comments leaking into modal
marcinpsk May 16, 2026
660fc4c
Add manual host/OOB role toggle on serial-match validation
marcinpsk May 16, 2026
e3fc163
Match OOB branch styling to Host branch on validation modal
marcinpsk May 16, 2026
6c6be9c
Fix Promote button -- bypass NetBox's broken data-bs-toggle handler
marcinpsk May 16, 2026
a2581bc
Fix Confirm Promote silently dismissing inner modal
marcinpsk May 16, 2026
b188956
test: align _load_job_results assertions with current call signature
marcinpsk May 16, 2026
8d82ed4
Render host/OOB pair on import table with consistent paired styling
marcinpsk May 16, 2026
4184268
Address code review on paired host/OOB import row styling
marcinpsk May 16, 2026
41d0c83
test: align _save_device assertions with 400/409 contract
marcinpsk May 17, 2026
1263676
refactor: promote librenms_id coercion to utils.coerce_librenms_id
marcinpsk May 17, 2026
0ff1846
fix: include primary_ip6 in OOB-via-IP candidacy gate
marcinpsk May 17, 2026
9353b23
fix(ip_helpers): scope lookup to global VRF and recover from race
marcinpsk May 17, 2026
10f102f
test: assert on view.post return value, not mock_render call_args
marcinpsk May 17, 2026
faed916
fix(settings): track auto_create_ipam_default in Save button state
marcinpsk May 17, 2026
1e6aa49
fix(import): refresh import_as_vm before merge-candidate detection
marcinpsk May 19, 2026
852fd55
fix(import): stop leaking document click listeners in mapping dropdowns
marcinpsk May 19, 2026
edca631
fix(import): treat libre OS "-" as no-platform in mapping-form include
marcinpsk May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .devcontainer/config/codespaces-configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"127.0.0.1",
"*",
]
# Development environment — logging config values is an accepted tradeoff here.
# CodeQL alert for this is dismissed intentionally.
print(f"🔗 Codespaces detected: {codespace_name}")
print(f"🔒 CSRF Trusted Origins: {CSRF_TRUSTED_ORIGINS}")
print(f"🌐 Allowed Hosts: {ALLOWED_HOSTS}")
Expand Down
26 changes: 25 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
> - [frontend.instructions.md](instructions/frontend.instructions.md) – applies to templates and static files
> - [background-jobs.instructions.md](instructions/background-jobs.instructions.md) – applies to `jobs.py`, import views, and import utilities
> - [sync.instructions.md](instructions/sync.instructions.md) – applies to sync views, base views, tables, and sync JS
> - [release.instructions.md](instructions/release.instructions.md) – applies to changelog, pyproject.toml, and `__init__.py` version bumps

## Architecture & Key Modules
- Plugin hooks into NetBox (Django 5) under `netbox_librenms_plugin/`; respect NetBox plugin APIs (`navigation.py`, `urls.py`, `api/`).
Expand Down Expand Up @@ -62,13 +63,36 @@
- `_get_safe_redirect_url(request)` validates referrer URLs to prevent open-redirect attacks.

### Permission Helpers for Background Jobs
- Background jobs run outside view context and cannot use view mixins. Use standalone helpers from `import_utils.py` (`check_user_permissions`, `require_permissions`). See `background-jobs.instructions.md` for details.
- Background jobs run outside view context and cannot use view mixins. Use standalone helpers from `import_utils/permissions.py` (`check_user_permissions`, `require_permissions`). See `background-jobs.instructions.md` for details.

### API & Navigation Permissions
- API endpoints use `LibreNMSPluginPermission` class in `api/views.py` (GET=view, others=change).
- Navigation menu (`navigation.py`) has 3 groups: **Settings** (Plugin Settings, Interface Mappings), **Import** (LibreNMS Import), **Status Check** (Site & Location Sync, Device Status, VM Status). All items use `permissions=[PERM_VIEW_PLUGIN]`.
- **Background job polling requires superuser** — non-superusers fall back to synchronous mode. See `background-jobs.instructions.md` for details.

## CodeQL & Security Patterns

### Clearing CodeQL `py/reflected-xss` false positives
When a view builds an `HttpResponse` from Django-template-rendered HTML (decoded via `.content.decode()`), CodeQL traces `request → template-render → HttpResponse` as reflected XSS even though Django templates auto-escape all user values.

**Correct fix:** use `format_html()` to compose the envelope and `mark_safe()` as a **trust assertion** on the inner HTML — CodeQL's Django taint model recognises this pattern and stops tracking the taint:

```python
from django.utils.html import format_html
from django.utils.safestring import mark_safe

modal_html = some_view.get(request, pk).content.decode("utf-8")
oob = format_html('<div id="target" hx-swap-oob="innerHTML">{}</div>', mark_safe(modal_html))
return HttpResponse(oob, content_type="text/html")
```

> **Important:** `mark_safe()` is a trust assertion, not a sanitizer — it tells Django "I guarantee this string is already safe HTML." Only use it when `modal_html` comes from a server-rendered Django view (whose templates auto-escape all user values). Never pass untrusted user input to `mark_safe()` — that would introduce real XSS.

**Do NOT** use `# lgtm[py/reflected-xss]` — that is LGTM.com legacy syntax and is **not** honoured by GitHub's modern CodeQL Action.

### URL converters
Always use `<int:pk>` (not `<str:pk>`) for numeric IDs in URL patterns. Django's `<int:>` converter auto-validates and returns 404 for non-integer values, eliminating the URL-parameter taint source that CodeQL otherwise flags.

## When in Doubt
- Check docs in `docs/development/` for structure, view inheritance, mixins, and template conventions before introducing new patterns.
- Review the existing sync views (e.g., `views/sync/interfaces.py`) as reference implementations for data flow and caching patterns.
Expand Down
18 changes: 10 additions & 8 deletions .github/instructions/background-jobs.instructions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
applyTo: "**/jobs.py,**/views/imports/**,**/import_utils.py,**/import_validation_helpers.py"
applyTo: "**/jobs.py,**/views/imports/**,**/import_utils/**,**/import_validation_helpers.py"
description: Background job architecture, import workflow, and task management patterns
---

Expand Down Expand Up @@ -69,13 +69,15 @@ Filter fields: `librenms_location`, `librenms_type`, `librenms_os`, `librenms_ho
- **`DeviceVCDetailsView`** (GET) — renders VC member details via `htmx/device_vc_details.html`.
- **`DeviceRoleUpdateView`**, **`DeviceClusterUpdateView`**, **`DeviceRackUpdateView`** (POST) — per-device dropdown updates. Apply selection to validation state and return re-rendered row via `render_device_row()`.

## Key Import Utilities (`import_utils.py`)
- `process_device_filters(filters, ...)` — fetches and validates devices from LibreNMS, returns list.
- `validate_device_for_import(device, ...)` — core validation function, produces validation state dict.
- `bulk_import_devices_shared(devices, user, ...)` — shared implementation between sync and background import.
- `bulk_import_vms(vm_imports, user, ...)` — VM import implementation.
- `fetch_device_with_cache(device_id, ...)` — retrieves/caches individual device data.
- Cache key functions: `get_validated_device_cache_key()`, `get_cache_metadata_key()`, `get_active_cached_searches()`, `get_import_device_cache_key()`.
## Key Import Utilities (`import_utils/` package)
`import_utils/` is a package; the `__init__.py` re-exports key functions so callers can still use `from import_utils import ...`.

- `filters.py` — `process_device_filters(filters, ...)`, `fetch_device_with_cache(device_id, ...)`.
- `device_operations.py` — `validate_device_for_import(device, ...)`, `bulk_import_devices_shared(devices, user, ...)`.
- `vm_operations.py` — `bulk_import_vms(vm_imports, user, ...)`.
- `cache.py` — `get_validated_device_cache_key()`, `get_cache_metadata_key()`, `get_active_cached_searches()`, `get_import_device_cache_key()`.
- `permissions.py` — `check_user_permissions(user, permissions)`, `require_permissions(user, permissions, action_description)`.
- `virtual_chassis.py` — `create_virtual_chassis_with_members()`, `_sync_module_bay_counter()`.

## Validation Helpers (`import_validation_helpers.py`)
Centralizes validation state mutation used by the role/cluster/rack update views:
Expand Down
3 changes: 2 additions & 1 deletion .github/instructions/frontend.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ description: Frontend patterns for templates, HTMX, and static assets
- All HTMX requests and `fetch()` calls must include a CSRF token. The standard pattern is `document.querySelector('[name=csrfmiddlewaretoken]').value` (from a hidden form input). The import JS also uses `getCookie('csrftoken')` as a fallback — prefer the hidden input approach for consistency.

## Modal Implementation
- Modals use Tabler (Bootstrap-like) but **without** `bootstrap.Modal` helpers.
- Modals try Bootstrap 5 native (`bootstrap.Modal`) first, falling back to manual DOM manipulation if unavailable. Both `librenms_sync.js` and `librenms_import.js` follow this pattern via `showModal()`/`hideModal()` helpers.
- Buttons target the `htmx-modal-content` element and JavaScript in `librenms_import.html` toggles the wrapper.
- Do not reintroduce `data-bs-toggle` or duplicate modal IDs.
- The import page uses `ModalManager` class and `filterModalManager` instance—always use this reference in fetch callbacks, not undefined `modalInstance` variables.
- Dismiss handlers (backdrop click, `data-bs-dismiss` buttons) are bound once per element to prevent stacking on repeated `showModal()` calls.

## JavaScript Fetch Patterns
- Always check `response.ok` before processing fetch responses to catch HTTP errors.
Expand Down
149 changes: 149 additions & 0 deletions .github/instructions/release.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
applyTo: "**/changelog.md,**/pyproject.toml,**/__init__.py"
---

# Release Workflow

This describes the standard release process for the NetBox LibreNMS Plugin. Follow these steps exactly when asked to create a new release.

## Branch Strategy

> **Both `develop` and `master` have branch protection.** All changes must go through pull requests — never push directly.

### Standard Flow (used for all releases)

1. **Create `release/X.Y.Z` branch** from develop
2. **Version bump commit** on the release branch
3. **PR `release/X.Y.Z` → develop** (version bump PR)
4. **PR `develop` → master** (release PR) — GitHub will auto-include any commits master is missing
5. **Tag `vX.Y.Z`** on master and create GitHub release
6. **Post-release:** master may now be ahead of develop (e.g. the merge commit). This resolves naturally — step 1 of the next release starts from the current develop, and the release PR (step 4) will reconcile any divergence.

## Files to Update

Three files must be updated in a single commit with message `Bump version to X.Y.Z and update changelog`:

### `netbox_librenms_plugin/__init__.py`
```python
__version__ = "X.Y.Z"
```

### `pyproject.toml`
```toml
version = "X.Y.Z"
```

### `docs/changelog.md`
Prepend a new section at the top (after `# Changelog`). Use the current date in `YYYY-MM-DD` format. Categories used (pick only those that apply):
- `### New Features`
- `### Improvements`
- `### Fixes`
- `### Development`
- `### Documentation`

Example:
```markdown
## X.Y.Z (YYYY-MM-DD)

### Fixes
* Description of fix (#PR_NUMBER)
```

## Version Bump PR (release/X.Y.Z → develop)

**Title:** `Bump version to X.Y.Z and update changelog`

**Body template:**
```markdown
## Summary
Bump version to X.Y.Z and update changelog for release.

## Motivation / Problem
- Maintenance / cleanup

Prepare release X.Y.Z with <brief description of what's included>.

## Scope of Change

- Config / settings
- Docs only

## How Was This Tested?

- Not tested: version bump and changelog only

## Risk Assessment
- No impact on existing users
- No code logic changes

## Backwards Compatibility
- No breaking changes
```

## Release PR (develop → master)

**Title:** `Release X.Y.Z`

**Body template:**
```markdown
## Summary
Release X.Y.Z — merge develop into master for PyPI release.

## Motivation / Problem
- <Bug | Feature | Maintenance / cleanup>

<One-line description of what this release contains, referencing PR numbers.>

## Scope of Change

<List applicable scopes from the PR template>

## Changes
<Bullet list of changes, each referencing PR numbers>
- Bump version to X.Y.Z

## How Was This Tested?

<Summarize testing from the included PRs>

## Risk Assessment
<Brief risk assessment>

## Backwards Compatibility
- No breaking changes
```

## GitHub Release Text

**Tag:** `vX.Y.Z` (create on master)
**Release title:** `vX.Y.Z`

**Body template:**
```markdown
## <Bug Fix Release | Feature Release | Maintenance Release>

<One-paragraph summary of the release. Explain what was wrong and what changed.>

### <Fixes | New Features | Improvements>

- <Change description> (#PR_NUMBER)

> ### Upgrade note
> - <Migration/collectstatic notes, or "No database migrations in this release."> Standard [update process](https://github.com/bonzo81/netbox-librenms-plugin#update) applies.

---

## All Changes
* <commit title> by @<author> in https://github.com/bonzo81/netbox-librenms-plugin/pull/<PR_NUMBER>
```

The "All Changes" section lists only the feature/fix PRs included in the release — not version bump or merge PRs.

## Checklist

- [ ] Version bumped in `__init__.py` and `pyproject.toml`
- [ ] Changelog updated in `docs/changelog.md`
- [ ] Version bump PR merged to develop
- [ ] Release PR merged to master
- [ ] Tag created on master
- [ ] GitHub release published
4 changes: 3 additions & 1 deletion .github/instructions/testing.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ description: Testing patterns and conventions for the NetBox LibreNMS plugin

## Test Coverage by Module
- `librenms_api.py` → `test_librenms_api.py`, `test_librenms_api_helpers.py`
- `import_utils.py`, `import_validation_helpers.py`, `utils.py` → `test_import_utils.py`, `test_import_validation_helpers.py`, `test_utils.py`
- `import_utils/` package (`filters.py`, `device_operations.py`, `vm_operations.py`, `cache.py`, `permissions.py`, `virtual_chassis.py`), `import_validation_helpers.py`, `utils.py` → `test_import_utils.py`, `test_import_validation_helpers.py`, `test_utils.py`
- `jobs.py`, `views/imports/list.py` → `test_background_jobs.py`
- `import_utils/bulk_import.py` → `test_coverage_bulk_import.py`
- Utility helpers (`utils.py` coverage tests) → `test_coverage_utils.py`
- Permission mixins, API permissions, constants → `test_permissions.py`
- VLAN API, mode detection, comparison, sync → `test_vlan_sync.py`
- `VlanAssignmentMixin`, VLAN enrichment → `test_interface_vlan_sync.py`
Expand Down
31 changes: 31 additions & 0 deletions contrib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Contrib: Example Mapping Files

This directory contains example YAML mapping files for bulk import into the
NetBox LibreNMS Plugin. Each file can be imported via the plugin's bulk import
feature in the NetBox UI.

## How to Import

1. Navigate to the mapping page (e.g., **LibreNMS → Device Type Mappings**)
2. Click the **Import** button (upload icon) in the top right
3. Select **YAML** format
4. Paste the contents of the relevant YAML file
5. Click **Submit**

## Available Mappings

| File | Description |
|------|-------------|
| `interface_type_mappings.yaml` | Maps LibreNMS interface types + speeds to NetBox interface types |
| `device_type_mappings.yaml` | Maps LibreNMS hardware strings to NetBox device types |
| `module_type_mappings.yaml` | Maps LibreNMS inventory model names to NetBox module types (incl. transceivers) |
| `module_bay_mappings.yaml` | Maps LibreNMS inventory container names to NetBox module bay names |
| `normalization_rules.yaml` | Regex-based string normalization applied before module type/bay lookups |
| `inventory_ignore_rules.yaml` | Suppresses phantom ENTITY-MIB entries (e.g. Cisco IOS-XR IDPROM artefacts) |
| `platform_mappings.yaml` | Maps LibreNMS platform strings to NetBox device platforms |

## Customisation

These files are **examples** — adjust values to match the device types, module
types, and interface types defined in your NetBox instance. The `netbox_*`
fields must reference objects that already exist in your NetBox.
31 changes: 31 additions & 0 deletions contrib/carrier_auto_install_rules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Carrier Auto-Install Rules
#
# Suggest a holder/carrier ModuleType to install when LibreNMS reports orphan
# child modules (e.g. CPM cards, mezzanines, MDAs) that have no matching NetBox
# bay because their parent carrier was never installed in NetBox.
#
# Import via: LibreNMS Plugin → Carrier Auto-Install Rules → Import
#
# Fields:
# manufacturer: Optional manufacturer name (exact). Empty = any vendor.
# device_type_pattern: Optional regex on device_type.model. Empty = any model.
# librenms_child_class: Exact entPhysicalClass of the orphan child (e.g. cpmModule).
# librenms_child_name_pattern: Regex on entPhysicalName of the orphan child.
# netbox_bay_name_pattern: Regex on candidate empty device-level bay name.
# carrier_module_type: NetBox ModuleType model (slug-style model field) to install.
# description: Optional description.
#
# Patterns use Python re.fullmatch() — they must match the entire string.
# When the chassis has at least one empty bay matching netbox_bay_name_pattern,
# an "Install Carrier" button appears on the module sync page (suggest-only,
# never auto-installed). Multiple matching empty bays produce one button each.

# Nokia 7750 SR-s chassis (e.g. SR-7s, SR-14s) report CPM cards in slots A/B
# but the physical CMA carrier that holds them is invisible to LibreNMS.
- manufacturer: Nokia
device_type_pattern: '^7750 SR-.*$'
librenms_child_class: cpmModule
librenms_child_name_pattern: '^Slot [AB]$'
netbox_bay_name_pattern: '^CMA$'
carrier_module_type: CMA2-7s
description: Install CMA2-7s carrier when CPM cards are reported orphaned
73 changes: 73 additions & 0 deletions contrib/device_type_mappings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Device Type Mappings
#
# Maps LibreNMS hardware strings to NetBox device types.
# Import via: LibreNMS Plugin > Device Type Mappings > Import
#
# Fields:
# librenms_hardware — Hardware string exactly as shown in LibreNMS
# netbox_device_type — NetBox DeviceType (matched by model name or ID)
# description — Optional note
#
# The librenms_hardware value is matched case-insensitively.
# These mappings are checked BEFORE the built-in part_number/model fallback.

# Juniper — LibreNMS reports verbose marketing names
- librenms_hardware: "Juniper MX480 Internet Backbone Router"
netbox_device_type: "MX480"
description: "Juniper MX480 chassis"

- librenms_hardware: "Juniper MX960 Internet Backbone Router"
netbox_device_type: "MX960"
description: "Juniper MX960 chassis"

- librenms_hardware: "Juniper MX304 Edge Router"
netbox_device_type: "MX304"
description: "Juniper MX304 edge router"

- librenms_hardware: "JNP10008 [PTX10008]"
netbox_device_type: "PTX10008"
description: "Juniper PTX10008 core router"

- librenms_hardware: "JNP7100-32C [ACX7100-32C]"
netbox_device_type: "ACX7100-32C"
description: "Juniper ACX7100-32C"

- librenms_hardware: "JNP7024 [ACX7024]"
netbox_device_type: "ACX7024"
description: "Juniper ACX7024"

- librenms_hardware: "Juniper JNP10008 Internet Backbone Router"
netbox_device_type: "PTX10008"
description: "Juniper PTX10008 (alternate hardware string)"

- librenms_hardware: "Juniper VRR Internet Backbone Router"
netbox_device_type: "VRR"
description: "Juniper Virtual Route Reflector"

# Nokia — model string matches directly in most cases
- librenms_hardware: "7750 SR-7s"
netbox_device_type: "7750 SR-7s"
description: "Nokia 7750 SR-7s service router"

# Cisco — often matches by part_number but not always
- librenms_hardware: "WS-C4900M"
netbox_device_type: "WS-C4900M"
description: "Cisco Catalyst 4900M"

# Cisco IOS XR
- librenms_hardware: "8201-SYS"
netbox_device_type: "8201"
description: "Cisco 8201 (hardware string differs from model)"

# UfiSpace — LibreNMS reports SONiC/ONIE platform names
- librenms_hardware: "x86-64-ufispace-s9610-36d-r0"
netbox_device_type: "S9610-36D"
description: "UfiSpace S9610-36D"

- librenms_hardware: "x86-64-ufispace-s9610-46dx-r0"
netbox_device_type: "S9610-46DX"
description: "UfiSpace S9610-46DX"

- librenms_hardware: "x86-64-ufispace-s9700-53dx-r9"
netbox_device_type: "S9700-53DX"
description: "UfiSpace S9700-53DX"
Loading