From 34be397be91f5964a7930cea0e26036b01e43a09 Mon Sep 17 00:00:00 2001 From: Teva Bouchet Date: Fri, 14 Aug 2026 15:21:18 +0200 Subject: [PATCH 01/14] Add N-Central plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Monitor N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues. Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/configValidation.json | 11 + plugins/N-Central/v1/custom_types.json | 30 ++ .../v1/dataStreams/customerActiveIssues.json | 58 +++ .../N-Central/v1/dataStreams/customers.json | 26 ++ .../v1/dataStreams/deviceAssets.json | 20 + .../v1/dataStreams/deviceLifecycleInfo.json | 25 ++ .../N-Central/v1/dataStreams/deviceList.json | 62 +++ .../deviceServiceMonitorStatus.json | 47 +++ plugins/N-Central/v1/dataStreams/devices.json | 26 ++ .../v1/dataStreams/scripts/deviceAssets.js | 124 ++++++ .../dataStreams/serverInfoAuthenticated.json | 19 + .../N-Central/v1/dataStreams/serviceOrgs.json | 26 ++ plugins/N-Central/v1/dataStreams/sites.json | 26 ++ .../N-Central/v1/dataStreams/soCustomers.json | 38 ++ .../customerPerspective.dash.json | 227 +++++++++++ .../devicePerspective.dash.json | 271 +++++++++++++ .../N-Central/v1/defaultContent/manifest.json | 9 + .../v1/defaultContent/overview.dash.json | 364 ++++++++++++++++++ .../N-Central/v1/defaultContent/scopes.json | 50 +++ .../serviceOrgPerspective.dash.json | 82 ++++ .../defaultContent/sitePerspective.dash.json | 90 +++++ plugins/N-Central/v1/docs/README.md | 59 +++ plugins/N-Central/v1/icon.svg | 8 + .../v1/indexDefinitions/default.json | 56 +++ plugins/N-Central/v1/metadata.json | 49 +++ plugins/N-Central/v1/preRequest.js | 92 +++++ plugins/N-Central/v1/ui.json | 23 ++ 27 files changed, 1918 insertions(+) create mode 100644 plugins/N-Central/v1/configValidation.json create mode 100644 plugins/N-Central/v1/custom_types.json create mode 100644 plugins/N-Central/v1/dataStreams/customerActiveIssues.json create mode 100644 plugins/N-Central/v1/dataStreams/customers.json create mode 100644 plugins/N-Central/v1/dataStreams/deviceAssets.json create mode 100644 plugins/N-Central/v1/dataStreams/deviceLifecycleInfo.json create mode 100644 plugins/N-Central/v1/dataStreams/deviceList.json create mode 100644 plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json create mode 100644 plugins/N-Central/v1/dataStreams/devices.json create mode 100644 plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js create mode 100644 plugins/N-Central/v1/dataStreams/serverInfoAuthenticated.json create mode 100644 plugins/N-Central/v1/dataStreams/serviceOrgs.json create mode 100644 plugins/N-Central/v1/dataStreams/sites.json create mode 100644 plugins/N-Central/v1/dataStreams/soCustomers.json create mode 100644 plugins/N-Central/v1/defaultContent/customerPerspective.dash.json create mode 100644 plugins/N-Central/v1/defaultContent/devicePerspective.dash.json create mode 100644 plugins/N-Central/v1/defaultContent/manifest.json create mode 100644 plugins/N-Central/v1/defaultContent/overview.dash.json create mode 100644 plugins/N-Central/v1/defaultContent/scopes.json create mode 100644 plugins/N-Central/v1/defaultContent/serviceOrgPerspective.dash.json create mode 100644 plugins/N-Central/v1/defaultContent/sitePerspective.dash.json create mode 100644 plugins/N-Central/v1/docs/README.md create mode 100644 plugins/N-Central/v1/icon.svg create mode 100644 plugins/N-Central/v1/indexDefinitions/default.json create mode 100644 plugins/N-Central/v1/metadata.json create mode 100644 plugins/N-Central/v1/preRequest.js create mode 100644 plugins/N-Central/v1/ui.json diff --git a/plugins/N-Central/v1/configValidation.json b/plugins/N-Central/v1/configValidation.json new file mode 100644 index 00000000..5732e140 --- /dev/null +++ b/plugins/N-Central/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Authenticate", + "dataStream": { "name": "serverInfoAuthenticated" }, + "required": true, + "error": "Could not authenticate with N-central. Check the Base URL and User-API Token are correct and that the token hasn't expired.", + "success": "Connected successfully." + } + ] +} diff --git a/plugins/N-Central/v1/custom_types.json b/plugins/N-Central/v1/custom_types.json new file mode 100644 index 00000000..b43f84c1 --- /dev/null +++ b/plugins/N-Central/v1/custom_types.json @@ -0,0 +1,30 @@ +[ + { + "name": "N-central Service Organization", + "sourceType": "N-central Service Organization", + "icon": "building", + "singular": "Service Organization", + "plural": "Service Organizations" + }, + { + "name": "N-central Customer", + "sourceType": "N-central Customer", + "icon": "briefcase", + "singular": "Customer", + "plural": "Customers" + }, + { + "name": "N-central Site", + "sourceType": "N-central Site", + "icon": "location-dot", + "singular": "Site", + "plural": "Sites" + }, + { + "name": "N-central Device", + "sourceType": "N-central Device", + "icon": "microchip", + "singular": "Device", + "plural": "Devices" + } +] diff --git a/plugins/N-Central/v1/dataStreams/customerActiveIssues.json b/plugins/N-Central/v1/dataStreams/customerActiveIssues.json new file mode 100644 index 00000000..804e74cc --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/customerActiveIssues.json @@ -0,0 +1,58 @@ +{ + "name": "customerActiveIssues", + "displayName": "Customer Active Issues", + "description": "Currently active issues and alerts for a customer", + "tags": ["Customer", "Alerts"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "api/org-units/{{object.rawId}}/active-issues", + "pathToData": "data", + "expandInnerObjects": true, + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "200" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 1 + }, + "out": { "realm": "queryArg", "path": "pageNumber" } + } + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Customer"] } }, + "metadata": [ + { "name": "orgUnitId", "shape": "number", "visible": false }, + { "name": "deviceId", "shape": "number", "visible": false }, + { + "name": "_extra.deviceName", + "displayName": "Device", + "shape": "string", + "role": "label", + "sourceId": "deviceId", + "sourceType": "N-central Device" + }, + { "name": "serviceName", "displayName": "Service", "shape": "string" }, + { "name": "serviceType", "displayName": "Service Type", "shape": "string" }, + { "name": "notificationState", "displayName": "Notification State", "shape": "number" }, + { + "name": "_extra.transitionTime", + "displayName": "Since", + "shape": "date", + "role": "timestamp" + }, + { "name": "_extra.licenseMode", "displayName": "License Mode", "shape": "string" }, + { + "name": "_extra.remoteControllable", + "displayName": "Remote Controllable", + "shape": "boolean" + }, + { "name": "serviceId", "shape": "number", "visible": false }, + { "name": "taskId", "shape": "number", "visible": false }, + { "name": "serviceItemId", "shape": "number", "visible": false }, + { "name": "_extra.customerTree.0", "shape": "string", "visible": false }, + { "name": "_extra.customerTree.1", "shape": "string", "visible": false }, + { "name": "_extra.customerTree.2", "shape": "string", "visible": false } + ], + "timeframes": false +} diff --git a/plugins/N-Central/v1/dataStreams/customers.json b/plugins/N-Central/v1/dataStreams/customers.json new file mode 100644 index 00000000..eec120dd --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/customers.json @@ -0,0 +1,26 @@ +{ + "name": "customers", + "displayName": "Customers", + "description": "Customers, one row per customer", + "tags": ["Organization"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/customers", + "pathToData": "data", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "200" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 1 + }, + "out": { "realm": "queryArg", "path": "pageNumber" } + } + }, + "matches": "none", + "metadata": [{ "pattern": ".*" }], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/N-Central/v1/dataStreams/deviceAssets.json b/plugins/N-Central/v1/dataStreams/deviceAssets.json new file mode 100644 index 00000000..b61aa819 --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/deviceAssets.json @@ -0,0 +1,20 @@ +{ + "name": "deviceAssets", + "displayName": "Device Assets", + "description": "Hardware and software inventory for a device", + "tags": ["Device", "Inventory"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "api/devices/{{object.rawId}}/assets", + "postRequestScript": "deviceAssets.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Device"] } }, + "metadata": [ + { "name": "category", "displayName": "Category", "shape": "string" }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "name": "id", "displayName": "ID", "shape": "string", "role": "id", "visible": false }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/N-Central/v1/dataStreams/deviceLifecycleInfo.json b/plugins/N-Central/v1/dataStreams/deviceLifecycleInfo.json new file mode 100644 index 00000000..3d8077cb --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/deviceLifecycleInfo.json @@ -0,0 +1,25 @@ +{ + "name": "deviceLifecycleInfo", + "displayName": "Device Lifecycle Info", + "description": "Warranty, lease, and hardware replacement details for a device", + "tags": ["Device", "Inventory"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "api/devices/{{object.rawId}}/assets/lifecycle-info" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Device"] } }, + "metadata": [ + { "name": "warrantyExpiryDate", "displayName": "Warranty Expiry Date", "shape": "date" }, + { "name": "leaseExpiryDate", "displayName": "Lease Expiry Date", "shape": "date" }, + { "name": "expectedReplacementDate", "displayName": "Expected Replacement Date", "shape": "date" }, + { "name": "purchaseDate", "displayName": "Purchase Date", "shape": "date" }, + { "name": "cost", "displayName": "Cost", "shape": ["number", { "decimalPlaces": 2 }] }, + { "name": "location", "displayName": "Location", "shape": "string" }, + { "name": "assetTag", "displayName": "Asset Tag", "shape": "string" }, + { "name": "description", "displayName": "Description", "shape": "string" }, + { "name": "updateWarrantyError", "displayName": "Update Warranty Error", "shape": "string" }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/N-Central/v1/dataStreams/deviceList.json b/plugins/N-Central/v1/dataStreams/deviceList.json new file mode 100644 index 00000000..ee1dbe78 --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/deviceList.json @@ -0,0 +1,62 @@ +{ + "name": "deviceList", + "displayName": "Devices", + "description": "Managed devices, optionally filtered to a customer or site, one row per device", + "tags": ["Customer", "Site", "Device"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "{{ (typeof site !== 'undefined' && site && site.length) ? 'api/org-units/' + (Array.isArray(site[0].rawId) ? site[0].rawId[0] : site[0].rawId) + '/devices' : ((typeof customer !== 'undefined' && customer && customer.length) ? 'api/org-units/' + (Array.isArray(customer[0].rawId) ? customer[0].rawId[0] : customer[0].rawId) + '/devices' : 'api/devices') }}", + "pathToData": "data", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 1 + }, + "out": { "realm": "queryArg", "path": "pageNumber" } + } + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "customer", + "label": "Customer (optional)", + "objectLimit": 1, + "matches": { + "sourceType": { "type": "oneOf", "values": ["N-central Customer"] } + } + }, + { + "type": "objects", + "name": "site", + "label": "Site (optional)", + "objectLimit": 1, + "matches": { + "sourceType": { "type": "oneOf", "values": ["N-central Site"] } + } + } + ], + "metadata": [ + { + "name": "longName", + "displayName": "Device", + "shape": "string", + "role": "label", + "sourceId": "deviceId", + "sourceType": "N-central Device" + }, + { "name": "deviceId", "shape": "number", "visible": false }, + { "name": "deviceClass", "displayName": "Device Class", "shape": "string" }, + { "name": "supportedOs", "displayName": "OS", "shape": "string" }, + { "name": "isProbe", "displayName": "Probe", "shape": "boolean" }, + { "name": "licenseMode", "displayName": "License Mode", "shape": "string" }, + { "name": "customerId", "shape": "number", "visible": false }, + { "name": "siteId", "shape": "number", "visible": false }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json b/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json new file mode 100644 index 00000000..2f64dd50 --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json @@ -0,0 +1,47 @@ +{ + "name": "deviceServiceMonitorStatus", + "displayName": "Device Service Monitor Status", + "description": "Current service monitor health status for a device", + "tags": ["Device", "Monitoring"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "api/devices/{{object.rawId}}/service-monitor-status", + "pathToData": "data" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Device"] } }, + "metadata": [ + { "name": "moduleName", "displayName": "Service", "shape": "string", "role": "label" }, + { "name": "taskIdent", "displayName": "Instance", "shape": "string" }, + { + "name": "stateStatus", + "displayName": "Status", + "shape": [ + "state", + { + "map": { + "success": ["Normal"], + "warning": ["Misconfigured", "Stale", "Warning"], + "error": ["Failed"], + "unmonitored": ["Disabled", "Unmanaged"] + } + } + ] + }, + { "name": "taskNote", "displayName": "Template", "shape": "string" }, + { + "name": "lastScanTime", + "displayName": "Last Scan", + "shape": ["date", { "inputPattern": "yyyy-MM-dd HH:mm:ss.SSS xx" }] + }, + { "name": "transitionTime", "displayName": "Status Changed", "shape": "date" }, + { "name": "timeToStale", "displayName": "Time to Stale (s)", "shape": "seconds" }, + { "name": "applianceName", "displayName": "Probe", "shape": "string" }, + { "name": "isManagedTask", "displayName": "Managed", "shape": "boolean" }, + { "name": "taskId", "displayName": "Task ID", "shape": "string", "role": "id", "visible": false }, + { "name": "serviceId", "displayName": "Service ID", "shape": "string", "visible": false }, + { "name": "serviceItemId", "displayName": "Service Item ID", "shape": "string", "visible": false }, + { "name": "applianceId", "displayName": "Appliance ID", "shape": "string", "visible": false } + ], + "timeframes": false +} diff --git a/plugins/N-Central/v1/dataStreams/devices.json b/plugins/N-Central/v1/dataStreams/devices.json new file mode 100644 index 00000000..e6032f20 --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/devices.json @@ -0,0 +1,26 @@ +{ + "name": "devices", + "displayName": "Devices", + "description": "Managed devices, one row per device", + "tags": ["Device"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/devices", + "pathToData": "data", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 1 + }, + "out": { "realm": "queryArg", "path": "pageNumber" } + } + }, + "matches": "none", + "metadata": [{ "pattern": ".*" }], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js b/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js new file mode 100644 index 00000000..9abd4cfc --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js @@ -0,0 +1,124 @@ +// dataStreams/scripts/deviceAssets.js +// +// GET api/devices/{deviceId}/assets returns a bag of asset categories, each +// either a singleton object (e.g. "os", "processor") or a { list: [...] } +// array (e.g. "application", "memory"). A near-duplicate set of the same +// category names is repeated under "_extra" with a different (usually +// complementary, occasionally overlapping) set of fields for the same +// entities. Flatten everything into one row per discrete asset item, tagged +// with which category it came from. +const body = (data && data.data) || {}; +const extra = body._extra || {}; + +// Organizational/hierarchy context, not device asset info - already covered +// by the customers/sites data streams. +const skipCategories = new Set(["customer", "site", "socustomer"]); + +function nameFor(category, item) { + return ( + item.displayname || + item.caption || + item.name || + item.description || + item.title || + item.model || + item.modelnumber || + item.volumename || + item.servicename || + item.longname || + item.netbiosname || + item.reportedos || + item.product || + item.sharename || + item.partnumber || + item.uniqueid || + category + ); +} + +const rows = []; +const counters = {}; + +function addItem(category, item) { + if (!item) { + return; + } + const { _index, ...details } = item; + const i = (counters[category] = (counters[category] || 0) + 1); + // Spread the raw fields first, then set our own columns last so a raw + // field that happens to share a name (e.g. "patch" items have their own + // "category" field, a UUID) can never clobber the synthetic ones below. + rows.push({ + ...details, + category, + name: nameFor(category, item), + id: `${category}-${i}`, + }); +} + +// Singleton categories describe the device itself; top-level and "_extra" +// hold different (non-overlapping) fields for the same entity, so merge them +// into a single row per category instead of emitting two. +["device", "computersystem", "os", "processor", "motherboard"].forEach( + (key) => { + const merged = Object.assign({}, body[key] || {}, extra[key] || {}); + if (Object.keys(merged).length > 0) { + addItem(key, merged); + } + }, +); + +// List categories: top-level and "_extra" entries sharing the same "_index" +// describe the same underlying asset with complementary (occasionally +// overlapping) fields, so merge them by "_index" the same way singleton +// categories are merged above. "_extra" fields win on conflicts. Entries with +// no matching "_index" on the other side are kept as-is. +const listKeys = new Set([ + ...Object.keys(body).filter( + (k) => k !== "_extra" && body[k] && Array.isArray(body[k].list), + ), + ...Object.keys(extra).filter( + (k) => extra[k] && Array.isArray(extra[k].list), + ), +]); + +listKeys.forEach((key) => { + if (skipCategories.has(key)) { + return; + } + const bodyList = (body[key] && body[key].list) || []; + const extraList = (extra[key] && extra[key].list) || []; + + const byIndex = new Map(); + const unindexed = []; + + bodyList.forEach((item) => { + if (item && item._index != null) { + byIndex.set(item._index, item); + } else { + unindexed.push(item); + } + }); + + extraList.forEach((item) => { + if (!item) { + return; + } + if (item._index != null && byIndex.has(item._index)) { + byIndex.set(item._index, { + ...byIndex.get(item._index), + ...item, + }); + } else if (item._index != null) { + byIndex.set(item._index, item); + } else { + unindexed.push(item); + } + }); + + [...byIndex.values(), ...unindexed].forEach((item) => + addItem(key, item), + ); +}); + +result = rows; diff --git a/plugins/N-Central/v1/dataStreams/serverInfoAuthenticated.json b/plugins/N-Central/v1/dataStreams/serverInfoAuthenticated.json new file mode 100644 index 00000000..b66447af --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/serverInfoAuthenticated.json @@ -0,0 +1,19 @@ +{ + "name": "serverInfoAuthenticated", + "displayName": "Service Organizations Probe", + "description": "Minimal authenticated call to confirm the User-API Token works", + "tags": ["System"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/service-orgs", + "getArgs": [ + { "key": "pageSize", "value": "1" }, + { "key": "pageNumber", "value": "1" } + ] + }, + "matches": "none", + "metadata": [{ "pattern": ".*" }], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/N-Central/v1/dataStreams/serviceOrgs.json b/plugins/N-Central/v1/dataStreams/serviceOrgs.json new file mode 100644 index 00000000..234fb51a --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/serviceOrgs.json @@ -0,0 +1,26 @@ +{ + "name": "serviceOrgs", + "displayName": "Service Organizations", + "description": "Service organizations, one row per organization", + "tags": ["Organization"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/service-orgs", + "pathToData": "data", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "200" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 1 + }, + "out": { "realm": "queryArg", "path": "pageNumber" } + } + }, + "matches": "none", + "metadata": [{ "pattern": ".*" }], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/N-Central/v1/dataStreams/sites.json b/plugins/N-Central/v1/dataStreams/sites.json new file mode 100644 index 00000000..806ba9f8 --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/sites.json @@ -0,0 +1,26 @@ +{ + "name": "sites", + "displayName": "Sites", + "description": "Sites, one row per site", + "tags": ["Organization"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/sites", + "pathToData": "data", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "200" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 1 + }, + "out": { "realm": "queryArg", "path": "pageNumber" } + } + }, + "matches": "none", + "metadata": [{ "pattern": ".*" }], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/N-Central/v1/dataStreams/soCustomers.json b/plugins/N-Central/v1/dataStreams/soCustomers.json new file mode 100644 index 00000000..8e4073a4 --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/soCustomers.json @@ -0,0 +1,38 @@ +{ + "name": "soCustomers", + "displayName": "Service Org Customers", + "description": "Customers belonging to a service organization, one row per customer", + "tags": ["Organization", "Customer"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "api/customers", + "pathToData": "data", + "getArgs": [{ "key": "select", "value": "parentId=={{object.rawId}}" }], + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 1 + }, + "out": { "realm": "queryArg", "path": "pageNumber" } + } + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Service Organization"] } }, + "metadata": [ + { + "name": "customerName", + "displayName": "Customer", + "shape": "string", + "role": "label", + "sourceId": "customerId", + "sourceType": "N-central Customer" + }, + { "name": "customerId", "shape": "number", "visible": false }, + { "name": "parentId", "shape": "number", "visible": false }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/N-Central/v1/defaultContent/customerPerspective.dash.json b/plugins/N-Central/v1/defaultContent/customerPerspective.dash.json new file mode 100644 index 00000000..19c856ae --- /dev/null +++ b/plugins/N-Central/v1/defaultContent/customerPerspective.dash.json @@ -0,0 +1,227 @@ +{ + "name": "Customer", + "schemaVersion": "1.5", + "variables": ["{{variables.[Customer]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "85228a4a-5eb9-49c4-9923-31340511877c", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Issues", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Customer]}}"], + "dataStream": { + "id": "{{dataStreams.customerActiveIssues}}", + "name": "customerActiveIssues", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Customers]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Customer]}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { "type": "count" }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "c9671910-5472-48be-84cb-5fae864dbc10", + "x": 0, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Customer Properties", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Customer]}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Customers]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Customer]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { "transpose": true } + } + } + } + }, + { + "i": "c8a71885-563d-4dde-a720-b2a8141d9ec1", + "x": 2, + "y": 2, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Issues by Service Type", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Customer]}}"], + "dataStream": { + "id": "{{dataStreams.customerActiveIssues}}", + "name": "customerActiveIssues", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["serviceType", "uniqueValues"]], + "aggregate": [{ "type": "count" }] + } + }, + "scope": { + "scope": "{{scopes.[Customers]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Customer]}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "serviceType_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "right", + "legendMode": "table" + } + } + } + } + }, + { + "i": "d3f72f91-e4ae-43ac-b2d9-a5a8ebdf45f5", + "x": 0, + "y": 6, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Issues", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Customer]}}"], + "dataStream": { + "id": "{{dataStreams.customerActiveIssues}}", + "name": "customerActiveIssues", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [["_extra.transitionTime", "desc"]] + } + }, + "scope": { + "scope": "{{scopes.[Customers]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Customer]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "_extra.deviceName", + "serviceName", + "serviceType", + "notificationState", + "_extra.transitionTime" + ], + "hiddenColumns": [ + "orgUnitId", + "deviceId", + "_extra.licenseMode", + "_extra.remoteControllable", + "serviceId", + "taskId", + "serviceItemId" + ] + } + } + } + } + }, + { + "i": "e43578f5-5ee6-430c-8414-7ee18f04b907", + "x": 0, + "y": 10, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Devices", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Customer]}}"], + "dataStream": { + "id": "{{dataStreams.deviceList}}", + "name": "deviceList", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "customer": { + "variable": "{{variables.[Customer]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Customers]}}" + } + } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "longName", + "deviceClass", + "supportedOs", + "licenseMode", + "isProbe" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/N-Central/v1/defaultContent/devicePerspective.dash.json b/plugins/N-Central/v1/defaultContent/devicePerspective.dash.json new file mode 100644 index 00000000..1985a538 --- /dev/null +++ b/plugins/N-Central/v1/defaultContent/devicePerspective.dash.json @@ -0,0 +1,271 @@ +{ + "name": "Device", + "schemaVersion": "1.5", + "variables": ["{{variables.[Device]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "05b21c91-f7b4-46b5-b328-c5aa8b930f14", + "x": 0, + "y": 0, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Device Properties", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Device]}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Device]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { "transpose": true } + } + } + } + }, + { + "i": "d15c7009-d882-487a-807e-3e8d74976060", + "x": 2, + "y": 0, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Lifecycle Info", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Device]}}"], + "dataStream": { + "id": "{{dataStreams.deviceLifecycleInfo}}", + "name": "deviceLifecycleInfo", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Device]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { "transpose": true } + } + } + } + }, + { + "i": "910e234f-ea9d-459a-82d3-9edc4a6e6cca", + "x": 0, + "y": 4, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Monitor Health", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Device]}}"], + "dataStream": { + "id": "{{dataStreams.deviceServiceMonitorStatus}}", + "name": "deviceServiceMonitorStatus", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Device]}}" + }, + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "labelColumn": "moduleName", + "stateColumn": "stateStatus", + "sublabel": "applianceName", + "linkColumn": "none", + "columns": 4 + } + } + } + } + }, + { + "i": "afadff99-f474-416e-864c-2fb785213429", + "x": 0, + "y": 8, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Asset Category Breakdown", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Device]}}"], + "dataStream": { + "id": "{{dataStreams.deviceAssets}}", + "name": "deviceAssets", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["category", "uniqueValues"]], + "aggregate": [{ "type": "count" }] + }, + "sort": { + "by": [["count", "desc"]], + "top": 10 + } + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Device]}}" + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "category_uniqueValues", + "yAxisData": ["count"], + "xAxisGroup": "none", + "xAxisLabel": "Category", + "yAxisLabel": "Items", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "showValue": true, + "showTotals": false, + "displayMode": "actual", + "horizontalLayout": "horizontal", + "grouping": false, + "range": { "type": "auto" } + } + } + } + } + }, + { + "i": "caac587c-745c-4cf6-9f4a-b717debbda19", + "x": 2, + "y": 8, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Monitor Status", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Device]}}"], + "dataStream": { + "id": "{{dataStreams.deviceServiceMonitorStatus}}", + "name": "deviceServiceMonitorStatus", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [["moduleName", "asc"]] + } + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Device]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "moduleName", + "stateStatus", + "lastScanTime", + "applianceName", + "transitionTime" + ], + "hiddenColumns": [ + "taskIdent", + "taskNote", + "timeToStale", + "isManagedTask" + ] + } + } + } + } + }, + { + "i": "5e436233-622a-41e2-af3f-67a8a4f2d9b8", + "x": 0, + "y": 12, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Device Assets", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Device]}}"], + "dataStream": { + "id": "{{dataStreams.deviceAssets}}", + "name": "deviceAssets", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [["category", "asc"]] + } + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Device]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["category", "name"], + "hiddenColumns": ["id"] + } + } + } + } + } + ] + } +} diff --git a/plugins/N-Central/v1/defaultContent/manifest.json b/plugins/N-Central/v1/defaultContent/manifest.json new file mode 100644 index 00000000..dc3b4d76 --- /dev/null +++ b/plugins/N-Central/v1/defaultContent/manifest.json @@ -0,0 +1,9 @@ +{ + "items": [ + { "name": "overview", "type": "dashboard" }, + { "name": "serviceOrgPerspective", "type": "dashboard" }, + { "name": "customerPerspective", "type": "dashboard" }, + { "name": "sitePerspective", "type": "dashboard" }, + { "name": "devicePerspective", "type": "dashboard" } + ] +} diff --git a/plugins/N-Central/v1/defaultContent/overview.dash.json b/plugins/N-Central/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..0c65f031 --- /dev/null +++ b/plugins/N-Central/v1/defaultContent/overview.dash.json @@ -0,0 +1,364 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "variables": [], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "862fe8ea-54fc-4955-bc46-23c829dbae0a", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Organizations", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Service Organizations]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { "type": "count" }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "8d22fe52-00fe-47da-b539-c61b1f4620ac", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Customers", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Customers]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { "type": "count" }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "d5132339-deb3-416a-a34a-cbea83fa148d", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Sites", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Sites]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { "type": "count" }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "b7601da2-ac87-4d47-8d72-3e6f4c1b4547", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Devices", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { "type": "count" }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "2fbbdba0-add2-4c43-8060-8e1739686151", + "x": 0, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Issues", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.customerActiveIssues}}", + "name": "customerActiveIssues", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Customers]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { "type": "count" }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "19b961fc-0ad8-4d29-9653-1bf0a34f1e77", + "x": 1, + "y": 2, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Services in Error State", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.deviceServiceMonitorStatus}}", + "name": "deviceServiceMonitorStatus", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { "column": "stateStatus", "operation": "equals", "value": "Failed" } + ] + } + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { "type": "count" }, + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "5ffceefb-2ba4-40a5-99ba-aba28afdbf36", + "x": 0, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Monitor Status", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.deviceServiceMonitorStatus}}", + "name": "deviceServiceMonitorStatus", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["stateStatus", "uniqueValues"]], + "aggregate": [{ "type": "count" }] + } + }, + "scope": { + "scope": "{{scopes.[Devices]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "stateStatus_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "right", + "legendMode": "table" + } + } + } + } + }, + { + "i": "d54f1094-36e2-4cb6-b842-385c190a062b", + "x": 2, + "y": 4, + "w": 2, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Issues by Service Type", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.customerActiveIssues}}", + "name": "customerActiveIssues", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["serviceType", "uniqueValues"]], + "aggregate": [{ "type": "count" }] + } + }, + "scope": { + "scope": "{{scopes.[Customers]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "serviceType_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "right", + "legendMode": "table" + } + } + } + } + }, + { + "i": "b87c5a1c-079d-40d4-96ad-291f993d32d4", + "x": 0, + "y": 8, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Issues", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.customerActiveIssues}}", + "name": "customerActiveIssues", + "pluginConfigId": "{{configId}}", + "sort": { + "by": [["_extra.transitionTime", "desc"]], + "top": 15 + } + }, + "scope": { + "scope": "{{scopes.[Customers]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "_extra.deviceName", + "serviceName", + "serviceType", + "notificationState", + "_extra.transitionTime" + ], + "hiddenColumns": [ + "orgUnitId", + "deviceId", + "_extra.licenseMode", + "_extra.remoteControllable", + "serviceId", + "taskId", + "serviceItemId" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/N-Central/v1/defaultContent/scopes.json b/plugins/N-Central/v1/defaultContent/scopes.json new file mode 100644 index 00000000..069ffcc8 --- /dev/null +++ b/plugins/N-Central/v1/defaultContent/scopes.json @@ -0,0 +1,50 @@ +[ + { + "name": "Service Organizations", + "matches": { + "sourceType": { "type": "oneOf", "values": ["N-central Service Organization"] } + }, + "variable": { + "name": "Service Organization", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Customers", + "matches": { + "sourceType": { "type": "oneOf", "values": ["N-central Customer"] } + }, + "variable": { + "name": "Customer", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Sites", + "matches": { + "sourceType": { "type": "oneOf", "values": ["N-central Site"] } + }, + "variable": { + "name": "Site", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Devices", + "matches": { + "sourceType": { "type": "oneOf", "values": ["N-central Device"] } + }, + "variable": { + "name": "Device", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/N-Central/v1/defaultContent/serviceOrgPerspective.dash.json b/plugins/N-Central/v1/defaultContent/serviceOrgPerspective.dash.json new file mode 100644 index 00000000..16bc5aa6 --- /dev/null +++ b/plugins/N-Central/v1/defaultContent/serviceOrgPerspective.dash.json @@ -0,0 +1,82 @@ +{ + "name": "Service Organization", + "schemaVersion": "1.5", + "variables": ["{{variables.[Service Organization]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "7da4be41-b6c7-472b-b78f-4af73f748244", + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Service Organization Properties", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Service Organization]}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Service Organizations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Service Organization]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { "transpose": true } + } + } + } + }, + { + "i": "f5ac90fd-701d-42e2-bb98-fd5e1894b4c6", + "x": 0, + "y": 4, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Customers", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Service Organization]}}"], + "dataStream": { + "id": "{{dataStreams.soCustomers}}", + "name": "soCustomers", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Service Organizations]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Service Organization]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["customerName"] + } + } + } + } + } + ] + } +} diff --git a/plugins/N-Central/v1/defaultContent/sitePerspective.dash.json b/plugins/N-Central/v1/defaultContent/sitePerspective.dash.json new file mode 100644 index 00000000..5f4e9fd5 --- /dev/null +++ b/plugins/N-Central/v1/defaultContent/sitePerspective.dash.json @@ -0,0 +1,90 @@ +{ + "name": "Site", + "schemaVersion": "1.5", + "variables": ["{{variables.[Site]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "307a6700-0b53-42a8-883c-01f4af4fd34e", + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Site Properties", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Site]}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Sites]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Site]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { "transpose": true } + } + } + } + }, + { + "i": "4b4f12a0-2002-4b6e-aaf5-f88c651bd765", + "x": 0, + "y": 4, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Devices", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "variables": ["{{variables.[Site]}}"], + "dataStream": { + "id": "{{dataStreams.deviceList}}", + "name": "deviceList", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "site": { + "variable": "{{variables.[Site]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Sites]}}" + } + } + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "longName", + "deviceClass", + "supportedOs", + "licenseMode", + "isProbe" + ] + } + } + } + } + } + ] + } +} diff --git a/plugins/N-Central/v1/docs/README.md b/plugins/N-Central/v1/docs/README.md new file mode 100644 index 00000000..825bc08b --- /dev/null +++ b/plugins/N-Central/v1/docs/README.md @@ -0,0 +1,59 @@ +Monitor your [N-able N-central](https://www.n-able.com/products/n-central) managed estate in SquaredUp — service organizations, customers, sites, and devices, along with device inventory, service monitoring status, lifecycle info, and active issues — via the [N-central REST API](https://developer.n-able.com/n-central/docs). + +> ⚠️ Requires an N-central server on a version that exposes the REST API (N-central 2023.9 or later). The legacy SOAP API is not supported by this plugin. + +## Setup + +You will need your N-central server's base URL and a **User-API Token**. + +1. Sign in to your N-central server as a user with API access. +2. Go to **Administration → User Management → Users**, click the user you want to use for API access, then go to the **API Access** tab. +3. Click **Generate JSON Web Token** and copy the token — it is shown only once. This is your **User-API Token**. +4. Paste your N-central server's base URL (e.g. `https://yourserver.n-able.com`) into the **Base URL** field, and the User-API Token into the **User-API Token** field. + +The plugin exchanges the User-API Token for a short-lived access token itself via `POST /api/auth/authenticate`, but only when the cached access token has expired — the token is cached and refreshed automatically between requests, so no manual token exchange or periodic refresh is needed. + +## Configuration fields + +| Field | What it is | Where to find it | Required | +| ----- | ---------- | ----------------- | -------- | +| **Base URL** | The base address of your N-central server. | Your browser's address bar when signed in to N-central. | Yes | +| **User-API Token** | A long-lived JWT used to automatically obtain access tokens for every API call. | Administration → User Management → Users → [user] → API Access → Generate JSON Web Token — see Setup above. | Yes | + +On save, the plugin calls `GET /api/service-orgs` (a minimal authenticated probe) to confirm the User-API Token works; an invalid, unreachable, or revoked token fails setup with an authentication error. + +## What this plugin monitors + +- **Organizational structure** — service organizations, customers, and sites, mirroring how your N-central estate is grouped. +- **Devices** — managed workstations, servers, network gear, mobile devices, and cloud services, with hardware/software inventory, service monitoring health, and warranty/lease/lifecycle info per device. +- **Active issues** — currently active alerts per customer, with the affected device and service named. + +The out-of-the-box dashboards include an estate-wide **Overview** plus a perspective for each **Service Organization**, **Customer**, **Site**, and **Device**. + +## Data streams + +- **Device Assets** — hardware and software inventory for a device, one row per asset item (network adapter, patch, service, memory module, etc.). +- **Device Service Monitor Status** — current health of every monitored service/task on a device. +- **Device Lifecycle Info** — warranty, lease, purchase, and replacement dates for a device's hardware. +- **Customer Active Issues** — currently active alerts for a customer, naming the affected device and service. +- **Devices** — managed devices, filterable by customer or by site. +- **Service Org Customers** — customers belonging to a service organization. + +## What gets indexed + +| Object type | API source | Represents | +| ----------- | ---------- | ---------- | +| **Service Organization** | `GET /api/service-orgs` | A top-level MSP business unit. | +| **Customer** | `GET /api/customers` | A managed client. | +| **Site** | `GET /api/sites` | An optional location-based sub-group of a customer. | +| **Device** | `GET /api/devices` | A managed workstation, server, network device, mobile device, or cloud service. | + +**Relationships:** each Customer links to its parent Service Organization; each Site links to its parent Customer; each Device links to its owning Customer, and — when it sits under a site rather than directly under the customer — to that Site as well. + +## Known limitations + +- **Service Organizations without data appear sparse** — the Service Organization perspective and its "Customers" tile depend on your N-central instance having Service Organizations configured; a single-SO or SO-less deployment will show little there even though Customers, Sites, and Devices are unaffected. +- **Sites are optional in N-central** — not every customer has sites configured; the Site perspective is limited to basic properties, as no site-specific monitoring data is exposed by the API. +- **Active issues have no documented severity mapping** — the API's `notificationState` field is a numeric code with no published severity scale, so it's shown as a raw number rather than a health indicator. +- **All data is current-state, not historical** — every stream reflects a snapshot at request time; the N-central REST API does not expose historical/time-series metrics, so no timeframe selection is available on any tile. +- **Read-only** — the plugin never creates, modifies, or deletes anything in N-central. diff --git a/plugins/N-Central/v1/icon.svg b/plugins/N-Central/v1/icon.svg new file mode 100644 index 00000000..d7f0a6ee --- /dev/null +++ b/plugins/N-Central/v1/icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/plugins/N-Central/v1/indexDefinitions/default.json b/plugins/N-Central/v1/indexDefinitions/default.json new file mode 100644 index 00000000..ffcad196 --- /dev/null +++ b/plugins/N-Central/v1/indexDefinitions/default.json @@ -0,0 +1,56 @@ +{ + "steps": [ + { + "name": "serviceOrgs", + "dataStream": { "name": "serviceOrgs" }, + "timeframe": "none", + "objectMapping": { + "id": "soId", + "name": "soName", + "type": { "value": "N-central Service Organization" } + } + }, + { + "name": "customers", + "dataStream": { "name": "customers" }, + "timeframe": "none", + "objectMapping": { + "id": "customerId", + "name": "customerName", + "type": { "value": "N-central Customer" }, + "properties": ["parentId"] + } + }, + { + "name": "sites", + "dataStream": { "name": "sites" }, + "timeframe": "none", + "objectMapping": { + "id": "siteId", + "name": "siteName", + "type": { "value": "N-central Site" }, + "properties": ["parentId"] + } + }, + { + "name": "devices", + "dataStream": { "name": "devices" }, + "timeframe": "none", + "objectMapping": { + "id": "deviceId", + "name": "longName", + "type": { "value": "N-central Device" }, + "properties": [ + "customerId", + "siteId", + "soId", + "orgUnitId", + "deviceClass", + "supportedOs", + "isProbe", + "licenseMode" + ] + } + } + ] +} diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json new file mode 100644 index 00000000..b5acfd83 --- /dev/null +++ b/plugins/N-Central/v1/metadata.json @@ -0,0 +1,49 @@ +{ + "name": "n-central", + "displayName": "N-central", + "version": "2.0.1", + "author": { "name": "arobavet", "type": "community" }, + "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", + "category": "Monitoring", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": ["rmm", "msp", "monitoring", "devices", "n-able", "n-central"], + "objectTypes": [ + "N-central Service Organization", + "N-central Customer", + "N-central Site", + "N-central Device" + ], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/N-Central/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/N-Central/v1", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "{{host}}", + "authMode": "none", + "headers": [ + { "key": "Accept", "value": "application/json" } + ], + "queryArgs": [], + "scriptingVariables": [ + { "key": "apiBaseUrl", "value": "{{host}}" }, + { "key": "userApiToken", "value": "{{userApiToken}}" } + ], + "preRequestScript": "preRequest.js", + "showScripting": true + } + } +} diff --git a/plugins/N-Central/v1/preRequest.js b/plugins/N-Central/v1/preRequest.js new file mode 100644 index 00000000..6895e719 --- /dev/null +++ b/plugins/N-Central/v1/preRequest.js @@ -0,0 +1,92 @@ +// N-central authenticates by exchanging a long-lived User-API Token (JWT) +// for a short-lived access token via POST /api/auth/authenticate. +// +// The access token is cached in `state` and reused until shortly before +// expiration. When expired, the User-API Token is exchanged again. +// +// The request path is also normalised to avoid duplicated slashes when +// the configured API base URL or endpoint contains a trailing slash. + +url.pathname = url.pathname.replace(/\/{2,}/g, "/"); + +// --- token ----------------------------------------------------------------------- + +if ( + typeof state?.token !== "string" || + (state?.expiryTime ?? 0) <= Date.now() +) { + const authUrl = `${String(secrets.apiBaseUrl|| "") + .trim() + .replace(/\/+$/, "")}/api/auth/authenticate`; + + const userApiToken = String(secrets.userApiToken || "").trim(); + + if (!userApiToken) { + api.report.error( + "N-central User-API Token is not configured." + ); + return; + } + + const request = { + method: "post", + headers: { + Accept: "application/json", + Authorization: `Bearer ${userApiToken}`, + }, + }; + + const response = await fetch(authUrl, request); + + if (!response.ok) { + if (response.status === 401 || response.status === 403) { + api.report.error( + `N-central rejected the User-API Token (HTTP ${response.status}). ` + + "Check that the User-API Token is valid and that the N-central account has API access." + ); + } else if (response.status === 404) { + api.report.error( + `N-central authentication endpoint was not found at ${authUrl} (HTTP 404). ` + + "Check the configured N-central API base URL." + ); + } else { + api.report.error( + `Could not obtain an N-central access token: HTTP ${response.status} ${response.statusText}` + ); + } + + return; + } + + const payload = await response.json(); + + const token = payload?.tokens?.access?.token; + const expirySeconds = payload?.tokens?.access?.expirySeconds; + + if (!token) { + api.report.error( + "N-central returned a successful authentication response but no access token was found." + ); + return; + } + + const lifetimeMs = + typeof expirySeconds === "number" + ? expirySeconds * 1000 + : 3600000; + + // Refresh 5 minutes before expiration so a long-running request is less + // likely to fail because the token expires mid-flight. Clamp the margin + // below lifetimeMs so short-lived tokens still get a future expiryTime. + const refreshMarginMs = Math.min(300000, Math.floor(lifetimeMs / 2)); + + state = { + ...state, + token, + expiryTime: Date.now() + lifetimeMs - refreshMarginMs, + }; +} + +// --- request --------------------------------------------------------------------- + +headers["Authorization"] = `Bearer ${state.token}`; \ No newline at end of file diff --git a/plugins/N-Central/v1/ui.json b/plugins/N-Central/v1/ui.json new file mode 100644 index 00000000..1847ba22 --- /dev/null +++ b/plugins/N-Central/v1/ui.json @@ -0,0 +1,23 @@ +[ + { + "type": "url", + "name": "host", + "label": "Base URL", + "placeholder": "https://yourserver.n-able.com", + "help": "Your N-central server's base address, with no trailing slash.", + "validation": { + "required": true, + "pattern": { + "value": "^https://[^\\s]+$", + "message": "Enter a valid https URL" + } + } + }, + { + "type": "password", + "name": "userApiToken", + "label": "User-API Token", + "help": "Generated from Administration → User Management → Users → [user] → API Access → Generate JSON Web Token.", + "validation": { "required": true } + } +] From 9bff9de93d59b377c4e344150a9b1001dc65bb78 Mon Sep 17 00:00:00 2001 From: Teva Bouchet Date: Sat, 15 Aug 2026 21:43:15 +0200 Subject: [PATCH 02/14] Address second round of review findings on N-Central plugin - Reset version to 1.0.0 to match the v1 directory, consistent with every other plugin in the repo (no prior public 1.x release existed) - Replace the separate customer/site pickers on the Devices stream with a single optional selector accepting either type, removing the ambiguity of conflicting selections; update the customer and site perspective dashboards to bind to the unified selector - Remove the two Overview dashboard tiles that queried the per-device deviceServiceMonitorStatus stream against the entire estate (no bulk equivalent exists in N-central's REST API), which would fire one HTTP request per managed device on every page load Co-Authored-By: Claude Sonnet 5 --- .../N-Central/v1/dataStreams/deviceList.json | 17 +--- .../customerPerspective.dash.json | 2 +- .../v1/defaultContent/overview.dash.json | 86 +------------------ .../defaultContent/sitePerspective.dash.json | 2 +- plugins/N-Central/v1/metadata.json | 2 +- 5 files changed, 8 insertions(+), 101 deletions(-) diff --git a/plugins/N-Central/v1/dataStreams/deviceList.json b/plugins/N-Central/v1/dataStreams/deviceList.json index ee1dbe78..5ee752d4 100644 --- a/plugins/N-Central/v1/dataStreams/deviceList.json +++ b/plugins/N-Central/v1/dataStreams/deviceList.json @@ -6,7 +6,7 @@ "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", - "endpointPath": "{{ (typeof site !== 'undefined' && site && site.length) ? 'api/org-units/' + (Array.isArray(site[0].rawId) ? site[0].rawId[0] : site[0].rawId) + '/devices' : ((typeof customer !== 'undefined' && customer && customer.length) ? 'api/org-units/' + (Array.isArray(customer[0].rawId) ? customer[0].rawId[0] : customer[0].rawId) + '/devices' : 'api/devices') }}", + "endpointPath": "{{ (typeof orgUnit !== 'undefined' && orgUnit && orgUnit.length) ? 'api/org-units/' + (Array.isArray(orgUnit[0].rawId) ? orgUnit[0].rawId[0] : orgUnit[0].rawId) + '/devices' : 'api/devices' }}", "pathToData": "data", "paging": { "mode": "offset", @@ -23,20 +23,11 @@ "ui": [ { "type": "objects", - "name": "customer", - "label": "Customer (optional)", + "name": "orgUnit", + "label": "Customer or Site (optional)", "objectLimit": 1, "matches": { - "sourceType": { "type": "oneOf", "values": ["N-central Customer"] } - } - }, - { - "type": "objects", - "name": "site", - "label": "Site (optional)", - "objectLimit": 1, - "matches": { - "sourceType": { "type": "oneOf", "values": ["N-central Site"] } + "sourceType": { "type": "oneOf", "values": ["N-central Customer", "N-central Site"] } } } ], diff --git a/plugins/N-Central/v1/defaultContent/customerPerspective.dash.json b/plugins/N-Central/v1/defaultContent/customerPerspective.dash.json index 19c856ae..7afb5ee8 100644 --- a/plugins/N-Central/v1/defaultContent/customerPerspective.dash.json +++ b/plugins/N-Central/v1/defaultContent/customerPerspective.dash.json @@ -198,7 +198,7 @@ "name": "deviceList", "pluginConfigId": "{{configId}}", "dataSourceConfig": { - "customer": { + "orgUnit": { "variable": "{{variables.[Customer]}}", "workspace": "{{workspaceId}}", "scope": "{{scopes.[Customers]}}" diff --git a/plugins/N-Central/v1/defaultContent/overview.dash.json b/plugins/N-Central/v1/defaultContent/overview.dash.json index 0c65f031..6dcdefad 100644 --- a/plugins/N-Central/v1/defaultContent/overview.dash.json +++ b/plugins/N-Central/v1/defaultContent/overview.dash.json @@ -178,93 +178,9 @@ } } }, - { - "i": "19b961fc-0ad8-4d29-9653-1bf0a34f1e77", - "x": 1, - "y": 2, - "w": 1, - "h": 2, - "moved": false, - "static": false, - "z": 0, - "config": { - "_type": "tile/data-stream", - "title": "Services in Error State", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "timeframe": "none", - "dataStream": { - "id": "{{dataStreams.deviceServiceMonitorStatus}}", - "name": "deviceServiceMonitorStatus", - "pluginConfigId": "{{configId}}", - "filter": { - "multiOperation": "and", - "filters": [ - { "column": "stateStatus", "operation": "equals", "value": "Failed" } - ] - } - }, - "scope": { - "scope": "{{scopes.[Devices]}}", - "workspace": "{{workspaceId}}" - }, - "visualisation": { - "type": "data-stream-scalar", - "config": { - "data-stream-scalar": { - "value": { "type": "count" }, - "comparisonColumn": "none" - } - } - } - } - }, - { - "i": "5ffceefb-2ba4-40a5-99ba-aba28afdbf36", - "x": 0, - "y": 4, - "w": 2, - "h": 4, - "moved": false, - "static": false, - "z": 0, - "config": { - "_type": "tile/data-stream", - "title": "Service Monitor Status", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "timeframe": "none", - "dataStream": { - "id": "{{dataStreams.deviceServiceMonitorStatus}}", - "name": "deviceServiceMonitorStatus", - "pluginConfigId": "{{configId}}", - "group": { - "by": [["stateStatus", "uniqueValues"]], - "aggregate": [{ "type": "count" }] - } - }, - "scope": { - "scope": "{{scopes.[Devices]}}", - "workspace": "{{workspaceId}}" - }, - "visualisation": { - "type": "data-stream-donut-chart", - "config": { - "data-stream-donut-chart": { - "valueColumn": "count", - "labelColumn": "stateStatus_uniqueValues", - "hideCenterValue": false, - "showValuesAsPercentage": false, - "legendPosition": "right", - "legendMode": "table" - } - } - } - } - }, { "i": "d54f1094-36e2-4cb6-b842-385c190a062b", - "x": 2, + "x": 0, "y": 4, "w": 2, "h": 4, diff --git a/plugins/N-Central/v1/defaultContent/sitePerspective.dash.json b/plugins/N-Central/v1/defaultContent/sitePerspective.dash.json index 5f4e9fd5..3580b834 100644 --- a/plugins/N-Central/v1/defaultContent/sitePerspective.dash.json +++ b/plugins/N-Central/v1/defaultContent/sitePerspective.dash.json @@ -61,7 +61,7 @@ "name": "deviceList", "pluginConfigId": "{{configId}}", "dataSourceConfig": { - "site": { + "orgUnit": { "variable": "{{variables.[Site]}}", "workspace": "{{workspaceId}}", "scope": "{{scopes.[Sites]}}" diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index b5acfd83..34197295 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-central", - "version": "2.0.1", + "version": "1.0.0", "author": { "name": "arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From 75104007b1760aa019199dcf13c03bcedc2950f8 Mon Sep 17 00:00:00 2001 From: Teva Bouchet Date: Sat, 15 Aug 2026 22:01:10 +0200 Subject: [PATCH 03/14] Address third round of review findings on N-Central plugin - Add a placeholder example to the userApiToken config field - Add the matching inputPattern to transitionTime (same custom non-ISO format as lastScanTime in the same response, confirmed via N-able's OpenAPI schema) - Fix taskId/serviceId/serviceItemId shape from string to number to match the raw N-central API (int32) and customerActiveIssues.json, so the fields correlate correctly across streams Co-Authored-By: Claude Sonnet 5 --- .../v1/dataStreams/deviceServiceMonitorStatus.json | 12 ++++++++---- plugins/N-Central/v1/metadata.json | 2 +- plugins/N-Central/v1/ui.json | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json b/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json index 2f64dd50..977e237e 100644 --- a/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json +++ b/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json @@ -34,13 +34,17 @@ "displayName": "Last Scan", "shape": ["date", { "inputPattern": "yyyy-MM-dd HH:mm:ss.SSS xx" }] }, - { "name": "transitionTime", "displayName": "Status Changed", "shape": "date" }, + { + "name": "transitionTime", + "displayName": "Status Changed", + "shape": ["date", { "inputPattern": "yyyy-MM-dd HH:mm:ss.SSS xx" }] + }, { "name": "timeToStale", "displayName": "Time to Stale (s)", "shape": "seconds" }, { "name": "applianceName", "displayName": "Probe", "shape": "string" }, { "name": "isManagedTask", "displayName": "Managed", "shape": "boolean" }, - { "name": "taskId", "displayName": "Task ID", "shape": "string", "role": "id", "visible": false }, - { "name": "serviceId", "displayName": "Service ID", "shape": "string", "visible": false }, - { "name": "serviceItemId", "displayName": "Service Item ID", "shape": "string", "visible": false }, + { "name": "taskId", "displayName": "Task ID", "shape": "number", "role": "id", "visible": false }, + { "name": "serviceId", "displayName": "Service ID", "shape": "number", "visible": false }, + { "name": "serviceItemId", "displayName": "Service Item ID", "shape": "number", "visible": false }, { "name": "applianceId", "displayName": "Appliance ID", "shape": "string", "visible": false } ], "timeframes": false diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 34197295..593a2baa 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-central", - "version": "1.0.0", + "version": "1.0.1", "author": { "name": "arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", diff --git a/plugins/N-Central/v1/ui.json b/plugins/N-Central/v1/ui.json index 1847ba22..2c567a0b 100644 --- a/plugins/N-Central/v1/ui.json +++ b/plugins/N-Central/v1/ui.json @@ -17,6 +17,7 @@ "type": "password", "name": "userApiToken", "label": "User-API Token", + "placeholder": "eyJhbGciOiJSUzI1NiJ9...", "help": "Generated from Administration → User Management → Users → [user] → API Access → Generate JSON Web Token.", "validation": { "required": true } } From 911aeed3c1ec93a6092cadb522e9e2c3d30a334e Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:50:24 +0200 Subject: [PATCH 04/14] Refine N-Central plugin labels and metadata Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/metadata.json | 4 ++-- plugins/N-Central/v1/ui.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 593a2baa..b9f61160 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,8 +1,8 @@ { "name": "n-central", - "displayName": "N-central", + "displayName": "N-Able N-central", "version": "1.0.1", - "author": { "name": "arobavet", "type": "community" }, + "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", "type": "hybrid", diff --git a/plugins/N-Central/v1/ui.json b/plugins/N-Central/v1/ui.json index 2c567a0b..9d9a350f 100644 --- a/plugins/N-Central/v1/ui.json +++ b/plugins/N-Central/v1/ui.json @@ -2,7 +2,7 @@ { "type": "url", "name": "host", - "label": "Base URL", + "label": "Server URL", "placeholder": "https://yourserver.n-able.com", "help": "Your N-central server's base address, with no trailing slash.", "validation": { @@ -16,7 +16,7 @@ { "type": "password", "name": "userApiToken", - "label": "User-API Token", + "label": "API Token", "placeholder": "eyJhbGciOiJSUzI1NiJ9...", "help": "Generated from Administration → User Management → Users → [user] → API Access → Generate JSON Web Token.", "validation": { "required": true } From 0f3e1b7434a80c9204977c3a8d063c7f09323456 Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 17:51:43 +0200 Subject: [PATCH 05/14] Bump N-Central plugin to 1.0.2 Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index b9f61160..83f61475 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.1", + "version": "1.0.2", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From e51665c1ba8ae70069783af265c69c69413be0ba Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 18:21:28 +0200 Subject: [PATCH 06/14] Derive stable asset id from _index in deviceAssets script Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js | 2 +- plugins/N-Central/v1/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js b/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js index 9abd4cfc..439ca5e0 100644 --- a/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js +++ b/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js @@ -52,7 +52,7 @@ function addItem(category, item) { ...details, category, name: nameFor(category, item), - id: `${category}-${i}`, + id: `${category}-${_index ?? i}`, }); } diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 83f61475..978bd9d2 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.2", + "version": "1.0.3", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From 197ec5f90e1934b49309e40f042321be12efd9e0 Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 18:38:46 +0200 Subject: [PATCH 07/14] Drop N-central prefix from source type names Applied consistently across metadata.json, custom_types.json, indexDefinitions, scopes, and all matching data streams. Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/custom_types.json | 16 ++++++++-------- .../v1/dataStreams/customerActiveIssues.json | 4 ++-- .../N-Central/v1/dataStreams/deviceAssets.json | 2 +- .../v1/dataStreams/deviceLifecycleInfo.json | 2 +- plugins/N-Central/v1/dataStreams/deviceList.json | 4 ++-- .../dataStreams/deviceServiceMonitorStatus.json | 2 +- .../N-Central/v1/dataStreams/soCustomers.json | 4 ++-- plugins/N-Central/v1/defaultContent/scopes.json | 8 ++++---- .../N-Central/v1/indexDefinitions/default.json | 8 ++++---- plugins/N-Central/v1/metadata.json | 10 +++++----- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plugins/N-Central/v1/custom_types.json b/plugins/N-Central/v1/custom_types.json index b43f84c1..aa6df796 100644 --- a/plugins/N-Central/v1/custom_types.json +++ b/plugins/N-Central/v1/custom_types.json @@ -1,28 +1,28 @@ [ { - "name": "N-central Service Organization", - "sourceType": "N-central Service Organization", + "name": "Service Organization", + "sourceType": "Service Organization", "icon": "building", "singular": "Service Organization", "plural": "Service Organizations" }, { - "name": "N-central Customer", - "sourceType": "N-central Customer", + "name": "Customer", + "sourceType": "Customer", "icon": "briefcase", "singular": "Customer", "plural": "Customers" }, { - "name": "N-central Site", - "sourceType": "N-central Site", + "name": "Site", + "sourceType": "Site", "icon": "location-dot", "singular": "Site", "plural": "Sites" }, { - "name": "N-central Device", - "sourceType": "N-central Device", + "name": "Device", + "sourceType": "Device", "icon": "microchip", "singular": "Device", "plural": "Devices" diff --git a/plugins/N-Central/v1/dataStreams/customerActiveIssues.json b/plugins/N-Central/v1/dataStreams/customerActiveIssues.json index 804e74cc..f4f14cf0 100644 --- a/plugins/N-Central/v1/dataStreams/customerActiveIssues.json +++ b/plugins/N-Central/v1/dataStreams/customerActiveIssues.json @@ -20,7 +20,7 @@ "out": { "realm": "queryArg", "path": "pageNumber" } } }, - "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Customer"] } }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Customer"] } }, "metadata": [ { "name": "orgUnitId", "shape": "number", "visible": false }, { "name": "deviceId", "shape": "number", "visible": false }, @@ -30,7 +30,7 @@ "shape": "string", "role": "label", "sourceId": "deviceId", - "sourceType": "N-central Device" + "sourceType": "Device" }, { "name": "serviceName", "displayName": "Service", "shape": "string" }, { "name": "serviceType", "displayName": "Service Type", "shape": "string" }, diff --git a/plugins/N-Central/v1/dataStreams/deviceAssets.json b/plugins/N-Central/v1/dataStreams/deviceAssets.json index b61aa819..b97b8c89 100644 --- a/plugins/N-Central/v1/dataStreams/deviceAssets.json +++ b/plugins/N-Central/v1/dataStreams/deviceAssets.json @@ -9,7 +9,7 @@ "endpointPath": "api/devices/{{object.rawId}}/assets", "postRequestScript": "deviceAssets.js" }, - "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Device"] } }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Device"] } }, "metadata": [ { "name": "category", "displayName": "Category", "shape": "string" }, { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, diff --git a/plugins/N-Central/v1/dataStreams/deviceLifecycleInfo.json b/plugins/N-Central/v1/dataStreams/deviceLifecycleInfo.json index 3d8077cb..148e7eae 100644 --- a/plugins/N-Central/v1/dataStreams/deviceLifecycleInfo.json +++ b/plugins/N-Central/v1/dataStreams/deviceLifecycleInfo.json @@ -8,7 +8,7 @@ "httpMethod": "get", "endpointPath": "api/devices/{{object.rawId}}/assets/lifecycle-info" }, - "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Device"] } }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Device"] } }, "metadata": [ { "name": "warrantyExpiryDate", "displayName": "Warranty Expiry Date", "shape": "date" }, { "name": "leaseExpiryDate", "displayName": "Lease Expiry Date", "shape": "date" }, diff --git a/plugins/N-Central/v1/dataStreams/deviceList.json b/plugins/N-Central/v1/dataStreams/deviceList.json index 5ee752d4..4cf30e8a 100644 --- a/plugins/N-Central/v1/dataStreams/deviceList.json +++ b/plugins/N-Central/v1/dataStreams/deviceList.json @@ -27,7 +27,7 @@ "label": "Customer or Site (optional)", "objectLimit": 1, "matches": { - "sourceType": { "type": "oneOf", "values": ["N-central Customer", "N-central Site"] } + "sourceType": { "type": "oneOf", "values": ["Customer", "Site"] } } } ], @@ -38,7 +38,7 @@ "shape": "string", "role": "label", "sourceId": "deviceId", - "sourceType": "N-central Device" + "sourceType": "Device" }, { "name": "deviceId", "shape": "number", "visible": false }, { "name": "deviceClass", "displayName": "Device Class", "shape": "string" }, diff --git a/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json b/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json index 977e237e..79b782eb 100644 --- a/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json +++ b/plugins/N-Central/v1/dataStreams/deviceServiceMonitorStatus.json @@ -9,7 +9,7 @@ "endpointPath": "api/devices/{{object.rawId}}/service-monitor-status", "pathToData": "data" }, - "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Device"] } }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Device"] } }, "metadata": [ { "name": "moduleName", "displayName": "Service", "shape": "string", "role": "label" }, { "name": "taskIdent", "displayName": "Instance", "shape": "string" }, diff --git a/plugins/N-Central/v1/dataStreams/soCustomers.json b/plugins/N-Central/v1/dataStreams/soCustomers.json index 8e4073a4..77e47406 100644 --- a/plugins/N-Central/v1/dataStreams/soCustomers.json +++ b/plugins/N-Central/v1/dataStreams/soCustomers.json @@ -20,7 +20,7 @@ "out": { "realm": "queryArg", "path": "pageNumber" } } }, - "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Service Organization"] } }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Service Organization"] } }, "metadata": [ { "name": "customerName", @@ -28,7 +28,7 @@ "shape": "string", "role": "label", "sourceId": "customerId", - "sourceType": "N-central Customer" + "sourceType": "Customer" }, { "name": "customerId", "shape": "number", "visible": false }, { "name": "parentId", "shape": "number", "visible": false }, diff --git a/plugins/N-Central/v1/defaultContent/scopes.json b/plugins/N-Central/v1/defaultContent/scopes.json index 069ffcc8..8814b9df 100644 --- a/plugins/N-Central/v1/defaultContent/scopes.json +++ b/plugins/N-Central/v1/defaultContent/scopes.json @@ -2,7 +2,7 @@ { "name": "Service Organizations", "matches": { - "sourceType": { "type": "oneOf", "values": ["N-central Service Organization"] } + "sourceType": { "type": "oneOf", "values": ["Service Organization"] } }, "variable": { "name": "Service Organization", @@ -14,7 +14,7 @@ { "name": "Customers", "matches": { - "sourceType": { "type": "oneOf", "values": ["N-central Customer"] } + "sourceType": { "type": "oneOf", "values": ["Customer"] } }, "variable": { "name": "Customer", @@ -26,7 +26,7 @@ { "name": "Sites", "matches": { - "sourceType": { "type": "oneOf", "values": ["N-central Site"] } + "sourceType": { "type": "oneOf", "values": ["Site"] } }, "variable": { "name": "Site", @@ -38,7 +38,7 @@ { "name": "Devices", "matches": { - "sourceType": { "type": "oneOf", "values": ["N-central Device"] } + "sourceType": { "type": "oneOf", "values": ["Device"] } }, "variable": { "name": "Device", diff --git a/plugins/N-Central/v1/indexDefinitions/default.json b/plugins/N-Central/v1/indexDefinitions/default.json index ffcad196..7281a940 100644 --- a/plugins/N-Central/v1/indexDefinitions/default.json +++ b/plugins/N-Central/v1/indexDefinitions/default.json @@ -7,7 +7,7 @@ "objectMapping": { "id": "soId", "name": "soName", - "type": { "value": "N-central Service Organization" } + "type": { "value": "Service Organization" } } }, { @@ -17,7 +17,7 @@ "objectMapping": { "id": "customerId", "name": "customerName", - "type": { "value": "N-central Customer" }, + "type": { "value": "Customer" }, "properties": ["parentId"] } }, @@ -28,7 +28,7 @@ "objectMapping": { "id": "siteId", "name": "siteName", - "type": { "value": "N-central Site" }, + "type": { "value": "Site" }, "properties": ["parentId"] } }, @@ -39,7 +39,7 @@ "objectMapping": { "id": "deviceId", "name": "longName", - "type": { "value": "N-central Device" }, + "type": { "value": "Device" }, "properties": [ "customerId", "siteId", diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 978bd9d2..177d80af 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.3", + "version": "1.0.4", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", @@ -11,10 +11,10 @@ "restrictedToPlatforms": [], "keywords": ["rmm", "msp", "monitoring", "devices", "n-able", "n-central"], "objectTypes": [ - "N-central Service Organization", - "N-central Customer", - "N-central Site", - "N-central Device" + "Service Organization", + "Customer", + "Site", + "Device" ], "links": [ { From 3dafd2851daa1324ca47144f0c77f6c2f9eb711e Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 18:43:42 +0200 Subject: [PATCH 08/14] Align README and config validation error with Server URL label Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/configValidation.json | 2 +- plugins/N-Central/v1/docs/README.md | 4 ++-- plugins/N-Central/v1/metadata.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/N-Central/v1/configValidation.json b/plugins/N-Central/v1/configValidation.json index 5732e140..73c7de5a 100644 --- a/plugins/N-Central/v1/configValidation.json +++ b/plugins/N-Central/v1/configValidation.json @@ -4,7 +4,7 @@ "displayName": "Authenticate", "dataStream": { "name": "serverInfoAuthenticated" }, "required": true, - "error": "Could not authenticate with N-central. Check the Base URL and User-API Token are correct and that the token hasn't expired.", + "error": "Could not authenticate with N-central. Check the Server URL and User-API Token are correct and that the token hasn't expired.", "success": "Connected successfully." } ] diff --git a/plugins/N-Central/v1/docs/README.md b/plugins/N-Central/v1/docs/README.md index 825bc08b..db5f00a8 100644 --- a/plugins/N-Central/v1/docs/README.md +++ b/plugins/N-Central/v1/docs/README.md @@ -9,7 +9,7 @@ You will need your N-central server's base URL and a **User-API Token**. 1. Sign in to your N-central server as a user with API access. 2. Go to **Administration → User Management → Users**, click the user you want to use for API access, then go to the **API Access** tab. 3. Click **Generate JSON Web Token** and copy the token — it is shown only once. This is your **User-API Token**. -4. Paste your N-central server's base URL (e.g. `https://yourserver.n-able.com`) into the **Base URL** field, and the User-API Token into the **User-API Token** field. +4. Paste your N-central server's base URL (e.g. `https://yourserver.n-able.com`) into the **Server URL** field, and the User-API Token into the **User-API Token** field. The plugin exchanges the User-API Token for a short-lived access token itself via `POST /api/auth/authenticate`, but only when the cached access token has expired — the token is cached and refreshed automatically between requests, so no manual token exchange or periodic refresh is needed. @@ -17,7 +17,7 @@ The plugin exchanges the User-API Token for a short-lived access token itself vi | Field | What it is | Where to find it | Required | | ----- | ---------- | ----------------- | -------- | -| **Base URL** | The base address of your N-central server. | Your browser's address bar when signed in to N-central. | Yes | +| **Server URL** | The base address of your N-central server. | Your browser's address bar when signed in to N-central. | Yes | | **User-API Token** | A long-lived JWT used to automatically obtain access tokens for every API call. | Administration → User Management → Users → [user] → API Access → Generate JSON Web Token — see Setup above. | Yes | On save, the plugin calls `GET /api/service-orgs` (a minimal authenticated probe) to confirm the User-API Token works; an invalid, unreachable, or revoked token fails setup with an authentication error. diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 177d80af..b4259186 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.4", + "version": "1.0.5", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From 11d41cc43833d0dc6a156f73591074e654669ad6 Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:01:25 +0200 Subject: [PATCH 09/14] Reduce customerActiveIssues page size to avoid timeouts on large customers Endpoint has no server-side filtering; smaller pages keep each request fast enough to avoid the per-request timeout on customers with many active issues. Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/dataStreams/customerActiveIssues.json | 2 +- plugins/N-Central/v1/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/N-Central/v1/dataStreams/customerActiveIssues.json b/plugins/N-Central/v1/dataStreams/customerActiveIssues.json index f4f14cf0..d2b4e36e 100644 --- a/plugins/N-Central/v1/dataStreams/customerActiveIssues.json +++ b/plugins/N-Central/v1/dataStreams/customerActiveIssues.json @@ -11,7 +11,7 @@ "expandInnerObjects": true, "paging": { "mode": "offset", - "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "200" }, + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "50" }, "offset": { "mode": "page", "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index b4259186..253b5986 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.5", + "version": "1.0.6", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From 914e9e1db68af70e48d8474818e0d5d50ab17fe5 Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:06:58 +0200 Subject: [PATCH 10/14] Raise devices import page size to avoid truncated Overview device count The unscoped devices stream paginates ~1200+ devices at pageSize=100 (~12 requests), which hits SquaredUp's overall fetch timeout and truncates the import, undercounting the Overview "Devices" tile. N-central's API allows pageSize up to 1000, cutting this to ~2 requests. Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/dataStreams/devices.json | 2 +- plugins/N-Central/v1/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/N-Central/v1/dataStreams/devices.json b/plugins/N-Central/v1/dataStreams/devices.json index e6032f20..d8d8f767 100644 --- a/plugins/N-Central/v1/dataStreams/devices.json +++ b/plugins/N-Central/v1/dataStreams/devices.json @@ -10,7 +10,7 @@ "pathToData": "data", "paging": { "mode": "offset", - "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "100" }, + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "1000" }, "offset": { "mode": "page", "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 253b5986..8199f5c6 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.6", + "version": "1.0.7", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From 607ed77b43d8caf97855ef0f82d5c619e2be725d Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:13:54 +0200 Subject: [PATCH 11/14] Fix wrong Devices count on Overview dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tile counted objects in the "Devices" scope via the generic properties/count mechanism, which caps at 500 matched objects — so the tile was permanently stuck at 500 regardless of the real device total (5297 on this tenant). Added a dedicated deviceCount stream that reads the "totalItems" field the API already returns in its pagination envelope (via a single pageSize=1 request), and pointed the Overview tile at it instead of the capped scope count. Co-Authored-By: Claude Sonnet 5 --- .../N-Central/v1/dataStreams/deviceCount.json | 17 +++++++++++++++++ .../v1/defaultContent/overview.dash.json | 11 ++++------- plugins/N-Central/v1/metadata.json | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 plugins/N-Central/v1/dataStreams/deviceCount.json diff --git a/plugins/N-Central/v1/dataStreams/deviceCount.json b/plugins/N-Central/v1/dataStreams/deviceCount.json new file mode 100644 index 00000000..d462d0f9 --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/deviceCount.json @@ -0,0 +1,17 @@ +{ + "name": "deviceCount", + "displayName": "Device Count", + "description": "Total number of managed devices, read from the API's own pagination total rather than counting individual objects", + "tags": ["Device"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/devices", + "getArgs": [{ "key": "pageSize", "value": "1" }], + "pathToData": "totalItems", + "paging": { "mode": "none" } + }, + "matches": "none", + "metadata": [{ "name": "result", "displayName": "Total Devices", "shape": "number" }], + "timeframes": false +} diff --git a/plugins/N-Central/v1/defaultContent/overview.dash.json b/plugins/N-Central/v1/defaultContent/overview.dash.json index 6dcdefad..bffc0ca1 100644 --- a/plugins/N-Central/v1/defaultContent/overview.dash.json +++ b/plugins/N-Central/v1/defaultContent/overview.dash.json @@ -125,18 +125,15 @@ "activePluginConfigIds": ["{{configId}}"], "timeframe": "none", "dataStream": { - "id": "datastream-properties", - "name": "properties" - }, - "scope": { - "scope": "{{scopes.[Devices]}}", - "workspace": "{{workspaceId}}" + "id": "{{dataStreams.deviceCount}}", + "name": "deviceCount", + "pluginConfigId": "{{configId}}" }, "visualisation": { "type": "data-stream-scalar", "config": { "data-stream-scalar": { - "value": { "type": "count" }, + "value": "result", "comparisonColumn": "none" } } diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 8199f5c6..28c134b7 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.7", + "version": "1.0.8", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From 9d697848de817f8ffb1688b5a12f1e4c6ebde298 Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:16:38 +0200 Subject: [PATCH 12/14] Raise customerActiveIssues page size to the API max (1000) Fewer round-trips per customer reduces total fetch time, which is the actual lever for the overall-fetch timeout (confirmed while fixing the devices import). Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/dataStreams/customerActiveIssues.json | 2 +- plugins/N-Central/v1/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/N-Central/v1/dataStreams/customerActiveIssues.json b/plugins/N-Central/v1/dataStreams/customerActiveIssues.json index d2b4e36e..c21b47b4 100644 --- a/plugins/N-Central/v1/dataStreams/customerActiveIssues.json +++ b/plugins/N-Central/v1/dataStreams/customerActiveIssues.json @@ -11,7 +11,7 @@ "expandInnerObjects": true, "paging": { "mode": "offset", - "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "50" }, + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "1000" }, "offset": { "mode": "page", "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 28c134b7..f203335a 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.8", + "version": "1.0.9", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From 157921a7aae621338410b90817ea2ebabc1f9644 Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 19:17:51 +0200 Subject: [PATCH 13/14] Raise deviceList page size to the API max (1000) Same endpoint family as devices/customerActiveIssues; fewer round-trips reduces total fetch time for customers/sites with many devices. Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/dataStreams/deviceList.json | 2 +- plugins/N-Central/v1/metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/N-Central/v1/dataStreams/deviceList.json b/plugins/N-Central/v1/dataStreams/deviceList.json index 4cf30e8a..f4a2e38e 100644 --- a/plugins/N-Central/v1/dataStreams/deviceList.json +++ b/plugins/N-Central/v1/dataStreams/deviceList.json @@ -10,7 +10,7 @@ "pathToData": "data", "paging": { "mode": "offset", - "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "100" }, + "pageSize": { "realm": "queryArg", "path": "pageSize", "value": "1000" }, "offset": { "mode": "page", "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index f203335a..96971d70 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "n-central", "displayName": "N-Able N-central", - "version": "1.0.9", + "version": "1.0.10", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.", "category": "Monitoring", From bdcec8a93814102395bf5cf1e14a123257c6d02f Mon Sep 17 00:00:00 2001 From: arobavet <69453569+arobavet@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:56:31 +0200 Subject: [PATCH 14/14] Update plugins/N-Central/v1/metadata.json Co-authored-by: Dave Clarke <02DClarke@gmail.com> --- plugins/N-Central/v1/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 96971d70..15a9e699 100644 --- a/plugins/N-Central/v1/metadata.json +++ b/plugins/N-Central/v1/metadata.json @@ -1,6 +1,6 @@ { "name": "n-central", - "displayName": "N-Able N-central", + "displayName": "N-able N-central", "version": "1.0.10", "author": { "name": "@arobavet", "type": "community" }, "description": "Monitor your N-able N-central managed estate — service organizations, customers, sites, devices, inventory, service status, and active issues.",