INT-1912: UniFi Network Polarity Integration — V1 (IP & MAC Lookup, Multi-Site, Block/Reconnect) - #1
Open
polarity-developer-agent[bot] wants to merge 3 commits into
Conversation
- Multi-site client and device lookup (IPv4, MAC Address)
- GET /v1/sites with 1-hour module-level cache
- Client search: GET /v1/sites/{siteId}/clients?filter=ipAddress.eq() or macAddress.eq()
- Device search: GET /v1/sites/{siteId}/devices?filter=... with 400 fallback to full list + client-side filter
- onMessage: BLOCK_CLIENT and RECONNECT_CLIENT actions via POST /v1/sites/{siteId}/clients/{clientId}/actions
- Ember block.js component: per-client isBlockingClient loading state and blockActionMessage feedback
- block.hbs: CollapsibleSection per client/device with p-key/p-value identity, network, traffic tables
- summary.hbs: type badge, status badge, hostname/device name, site name pills
- validateOptions: URL trailing slash enforcement, API key required check
- handleRestError: 200/404/401/403/400/network error dispatch (efficient-ip pattern)
- package.json: postman-request, async, lodash, polarity-integration-utils
- CI: release-current-version + run-int-dev-checklist workflows (greynoise reference)
config/config.js
- Add missing top-level fields: name, acronym, description, entityTypes,
defaultColor, styles, onDemandOnly, block, and request sections
- Add request section (cert/key/proxy/rejectUnauthorized) — absence caused
startup() to throw 'cannot destructure undefined' on every server start
- Set rejectUnauthorized: false (UniFi controllers use self-signed certs)
config/config.json
- Was shipped as {} — silent install failure on the Polarity server
- Fully populate as exact mirror of config.js (all 4 options, request,
logging, block, styles, entityTypes, defaultColor, onDemandOnly)
- Add real UUID v4: dd4d5ee5-2097-4584-9af4-2e352aa74ffe
- Add reducer.details.file pointer to ./reducers/details.json
reducers/details.json (new file)
- Create pipeline DSL with copy ops for client_names, client_ips,
client_macs, client_statuses, client_sites, device_names, device_models,
device_states, device_sites + prune empty_values pass
styles/styles.less (new file)
- Create scoped LESS required by config.js styles[] reference
- Collapsible section toggle, item entries, status colours (connected/
blocked/updating), action button area, working spinner, error banner
components/block.js
- Add init() with this._super(...arguments) — required by PolarityComponent
- Move isBlocking and actionMessage maps inside init() under block._state
so they are per-instance, not class-level shared objects
- Add hasClients and hasDevices computed properties to guard {{#each}}
- Route all action state through block._state.isBlocking / actionMessage
for proper Ember reactivity
templates/block.hbs
- Remove {{#block-section}} — not a real Polarity component; rendered blank
- Remove {{#each}} loops without has* computed property guards
- Replace (not-eq ...) with (not (eq ...)) — 'not-eq' is not a registered
Ember helper; causes silent blank panel at runtime
- Remove format-uptime and format-bytes helpers — not registered in
Polarity's Ember build; show raw uptime seconds and remove byte display
- Remove @index inside {{#each}} — not supported in Polarity's Ember build
- Rewrite using standard p-key/p-value rows, fa-icon, collapsible sections
via block._state.showClients / showDevices + toggleSection action
- Route action loading/message state through block._state.isBlocking and
block._state.actionMessage
integration.js
- _searchClients: explicitly normalise clientId from c.id || c.clientId ||
c._id — UniFi API returns UUID as 'id'; missing mapping broke all
block/reconnect actions silently
- _searchDevices: remove devices.length > 1 heuristic filter guard;
always apply client-side filter to handle controllers that ignore the
filter query param regardless of result count
- _isEntityBlocklisted: parse comma-separated blocklist string into an
array before comparison; previous _.includes() on raw string did a
substring check producing false-positive blocklist hits
- _setupRegexBlocklists: add null-guard (options.ipBlocklistRegex || '')
before accessing .length to prevent TypeError on undefined option
The UniFi Network Integration API v1 returns the site identifier in the 'id' field (UUID), not 'siteId'. Reading site.siteId produced undefined, which was interpolated as the literal string 'undefined' into the URL: GET /v1/sites/undefined/clients?filter=... This triggered the API error: 'undefined' is not a valid 'siteId' value. Fix: read site.id in doLookup when extracting the siteId for _searchClients and _searchDevices. All downstream uses (enriched client/device objects, onMessage BLOCK_CLIENT/RECONNECT_CLIENT payloads via client.siteId) are correct — they all read the enriched property set from this same extraction point.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the full V1 UniFi Network Polarity integration as specified in INT-1912.
Security analysts can hover over any IPv4 or MAC Address in Polarity to see real-time context from their UniFi Network controller, and block or reconnect suspicious clients directly from the overlay.
Files Created (13 total)
integration.jsstartup,doLookup,onMessage,validateOptions. Multi-site fan-out viaasync.parallelLimit. Module-level sites cache (1 hr TTL).config/config.jsurl(adminOnly text),apiKey(adminOnly password),blocklist,ipBlocklistRegexconfig/config.json{}components/block.jsisBlockingClientloading state andblockActionMessageinline feedbackcomponents/summary.jsblock.data.detailstemplates/block.hbsCollapsibleSectionper client/device with identity, network, traffic tables and block/reconnect buttonstemplates/summary.hbsreducers/index.jsmodule.exports = {}package.jsonpostman-request,async,lodash,polarity-integration-utils.github/workflows/release-current-version.yml.github/workflows/run-int-dev-checklist.yml.gitignoreREADME.mdAPI Coverage
GET /v1/sitessitesCache+sitesCacheExpiry)GET /v1/sites/{siteId}/clients?filter=ipAddress.eq(...)GET /v1/sites/{siteId}/clients?filter=macAddress.eq(...)GET /v1/sites/{siteId}/devices?filter=...POST /v1/sites/{siteId}/clients/{clientId}/actionsblockandreconnectKey Design Decisions
N_sites × 2parallel tasks (clients + devices per site) viaasync.parallelLimit(10)clientIdUUID: Stored on everydetails.clients[]element soonMessagecan access it for block/reconnectGET /devicesfull list + client-side filtervalidateOptionsrejects URLs ending in/efficient-ippattern exactlyReference Patterns Used
postman-requestdefaults,requestWithDefaultspolarityio/efficient-ipIGNORED_IPS,_isInvalidEntity,_isEntityBlocklisted,_setupRegexBlocklistspolarityio/efficient-ipasync.parallelLimitfan-out, callback-style taskpolarityio/efficient-iphandleRestErrorstatus dispatchpolarityio/efficient-iponMessage+switch (payload.action)polarityio/redminepolarityio/greynoiseTesting Checklist
npm installcompletes without errorsvalidateOptionsvalidateOptionsdoLookup; cache hit on second callsiteId+siteNameattachedstatuschanges toBLOCKEDin overlaystatuschanges toCONNECTEDin overlay401→ "Invalid API Key" user-facing error403→ "Forbidden" user-facing errorCloses INT-1912