From 0a011bb6491abafb8a67ba891e6dd085c8b44fd9 Mon Sep 17 00:00:00 2001 From: Teva Bouchet Date: Fri, 14 Aug 2026 13:21:25 +0200 Subject: [PATCH 1/3] Add N-Central and WasabiWACM plugins Adds two new community plugins: N-central (RMM monitoring for service orgs, customers, sites, and devices) and Wasabi WACM (Wasabi Account Control Manager account, storage and billing monitoring). 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 | 60 +++ .../deviceServiceMonitorStatus.json | 47 +++ plugins/N-Central/v1/dataStreams/devices.json | 26 ++ .../v1/dataStreams/scripts/deviceAssets.js | 93 +++++ .../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 | 90 +++++ plugins/N-Central/v1/ui.json | 23 ++ plugins/WasabiWACM/v1/configValidation.json | 11 + plugins/WasabiWACM/v1/custom_types.json | 37 ++ .../WasabiWACM/v1/dataStreams/accounts.json | 36 ++ .../v1/dataStreams/channelAccounts.json | 30 ++ .../controlAccountBucketUtilization.json | 52 +++ .../v1/dataStreams/controlAccountSummary.json | 41 ++ .../controlAccountUsageHistory.json | 52 +++ .../v1/dataStreams/controlAccounts.json | 30 ++ .../WasabiWACM/v1/dataStreams/members.json | 36 ++ .../subAccountBucketUtilization.json | 52 +++ .../v1/dataStreams/subAccountInvoices.json | 174 +++++++++ .../v1/dataStreams/subAccountSummary.json | 39 ++ .../dataStreams/subAccountUsageHistory.json | 52 +++ .../v1/dataStreams/subAccounts.json | 32 ++ .../v1/dataStreams/wacmConfigValidation.json | 15 + .../channelAccountDashboard.dash.json | 49 +++ .../controlAccountDashboard.dash.json | 299 ++++++++++++++ .../v1/defaultContent/manifest.json | 9 + .../v1/defaultContent/overview.dash.json | 291 ++++++++++++++ .../WasabiWACM/v1/defaultContent/scopes.json | 50 +++ .../standaloneAccountDashboard.dash.json | 49 +++ .../subAccountDashboard.dash.json | 344 +++++++++++++++++ plugins/WasabiWACM/v1/docs/README.md | 61 +++ plugins/WasabiWACM/v1/icon.svg | 7 + .../v1/indexDefinitions/default.json | 160 ++++++++ plugins/WasabiWACM/v1/metadata.json | 56 +++ plugins/WasabiWACM/v1/ui.json | 20 + 54 files changed, 3967 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 create mode 100644 plugins/WasabiWACM/v1/configValidation.json create mode 100644 plugins/WasabiWACM/v1/custom_types.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/accounts.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/channelAccounts.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/controlAccountBucketUtilization.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/controlAccountSummary.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/controlAccountUsageHistory.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/controlAccounts.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/members.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/subAccountBucketUtilization.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/subAccountInvoices.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/subAccountSummary.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/subAccountUsageHistory.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/subAccounts.json create mode 100644 plugins/WasabiWACM/v1/dataStreams/wacmConfigValidation.json create mode 100644 plugins/WasabiWACM/v1/defaultContent/channelAccountDashboard.dash.json create mode 100644 plugins/WasabiWACM/v1/defaultContent/controlAccountDashboard.dash.json create mode 100644 plugins/WasabiWACM/v1/defaultContent/manifest.json create mode 100644 plugins/WasabiWACM/v1/defaultContent/overview.dash.json create mode 100644 plugins/WasabiWACM/v1/defaultContent/scopes.json create mode 100644 plugins/WasabiWACM/v1/defaultContent/standaloneAccountDashboard.dash.json create mode 100644 plugins/WasabiWACM/v1/defaultContent/subAccountDashboard.dash.json create mode 100644 plugins/WasabiWACM/v1/docs/README.md create mode 100644 plugins/WasabiWACM/v1/icon.svg create mode 100644 plugins/WasabiWACM/v1/indexDefinitions/default.json create mode 100644 plugins/WasabiWACM/v1/metadata.json create mode 100644 plugins/WasabiWACM/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..9edfb020 --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/deviceList.json @@ -0,0 +1,60 @@ +{ + "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)", + "matches": { + "sourceType": { "type": "oneOf", "values": ["N-central Customer"] } + } + }, + { + "type": "objects", + "name": "site", + "label": "Site (optional)", + "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..53886b4a --- /dev/null +++ b/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js @@ -0,0 +1,93 @@ +// 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: prefer "_extra"'s version when both exist (it's a strict +// superset for "application"); otherwise use whichever side has the list. +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 list = + extra[key] && Array.isArray(extra[key].list) + ? extra[key].list + : body[key] && body[key].list; + (list || []).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..5eea9921 --- /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 2022.1 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 on every request (via `POST /api/auth/authenticate`), caching and refreshing it automatically — no manual token exchange or periodic refresh 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 an authenticated system-health endpoint 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..744a4449 --- /dev/null +++ b/plugins/N-Central/v1/metadata.json @@ -0,0 +1,49 @@ +{ + "name": "n-central", + "displayName": "N-central", + "version": "2.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", + "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/NCentral/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/NCentral/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..c9bf23a2 --- /dev/null +++ b/plugins/N-Central/v1/preRequest.js @@ -0,0 +1,90 @@ +// 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; + + state = { + ...state, + token, + + // Refresh 5 minutes before expiration so a long-running request + // is less likely to fail because the token expires mid-flight. + expiryTime: Date.now() + lifetimeMs - 300000, + }; +} + +// --- 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 } + } +] diff --git a/plugins/WasabiWACM/v1/configValidation.json b/plugins/WasabiWACM/v1/configValidation.json new file mode 100644 index 00000000..0862c5a8 --- /dev/null +++ b/plugins/WasabiWACM/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Authenticate", + "dataStream": { "name": "wacmConfigValidation" }, + "required": true, + "error": "Could not authenticate. Check your username and API key are correct and have not been regenerated.", + "success": "Connected successfully." + } + ] +} diff --git a/plugins/WasabiWACM/v1/custom_types.json b/plugins/WasabiWACM/v1/custom_types.json new file mode 100644 index 00000000..8dd8163c --- /dev/null +++ b/plugins/WasabiWACM/v1/custom_types.json @@ -0,0 +1,37 @@ +[ + { + "name": "Wasabi Standalone Account", + "sourceType": "Wasabi Standalone Account", + "icon": "id-card", + "singular": "Standalone Account", + "plural": "Standalone Accounts" + }, + { + "name": "Wasabi Control Account", + "sourceType": "Wasabi Control Account", + "icon": "building-columns", + "singular": "Control Account", + "plural": "Control Accounts" + }, + { + "name": "Wasabi Channel Account", + "sourceType": "Wasabi Channel Account", + "icon": "handshake", + "singular": "Channel Account", + "plural": "Channel Accounts" + }, + { + "name": "Wasabi Sub-Account", + "sourceType": "Wasabi Sub-Account", + "icon": "building", + "singular": "Sub-Account", + "plural": "Sub-Accounts" + }, + { + "name": "Wasabi Member", + "sourceType": "Wasabi Member", + "icon": "user", + "singular": "Member", + "plural": "Members" + } +] diff --git a/plugins/WasabiWACM/v1/dataStreams/accounts.json b/plugins/WasabiWACM/v1/dataStreams/accounts.json new file mode 100644 index 00000000..6a43bed6 --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/accounts.json @@ -0,0 +1,36 @@ +{ + "name": "accounts", + "displayName": "Standalone Accounts", + "description": "All Wasabi Standalone Accounts, one row per account", + "tags": ["Accounts"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/accounts", + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": "none", + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "role": "id" }, + { + "name": "name", + "displayName": "Name", + "computed": true, + "valueExpression": "{{ $['companyName'] || (($['firstName'] || '') + ' ' + ($['lastName'] || '')).trim() }}", + "role": "label" + }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/WasabiWACM/v1/dataStreams/channelAccounts.json b/plugins/WasabiWACM/v1/dataStreams/channelAccounts.json new file mode 100644 index 00000000..9e8bd96e --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/channelAccounts.json @@ -0,0 +1,30 @@ +{ + "name": "channelAccounts", + "displayName": "Channel Accounts", + "description": "All Wasabi Channel Accounts, one row per account", + "tags": ["Accounts"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/channel-accounts", + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": "none", + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "role": "id" }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/WasabiWACM/v1/dataStreams/controlAccountBucketUtilization.json b/plugins/WasabiWACM/v1/dataStreams/controlAccountBucketUtilization.json new file mode 100644 index 00000000..67f1d882 --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/controlAccountBucketUtilization.json @@ -0,0 +1,52 @@ +{ + "name": "controlAccountBucketUtilization", + "displayName": "Control Account Bucket Utilization", + "description": "Per-bucket storage utilization for a control account", + "tags": ["Accounts", "Usage"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/control-accounts/{{object.rawId}}/buckets", + "getArgs": [ + { "key": "from", "value": "{{timeframe.start.split('T')[0]}}" }, + { "key": "to", "value": "{{timeframe.end.split('T')[0]}}" } + ], + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Wasabi Control Account"] } }, + "metadata": [ + { "name": "name", "displayName": "Bucket Name", "shape": "string", "role": "label" }, + { "name": "region", "displayName": "Region", "shape": "string" }, + { "name": "bucketNumber", "displayName": "Bucket Number", "shape": "string" }, + { "name": "startTime", "displayName": "Start Time", "shape": "date", "role": "timestamp" }, + { "name": "endTime", "displayName": "End Time", "shape": "date" }, + { "name": "activeStorage", "displayName": "Active Storage", "shape": "number" }, + { "name": "deletedStorage", "displayName": "Deleted Storage", "shape": "number" }, + { "name": "activeObjects", "displayName": "Active Objects", "shape": "number" }, + { "name": "deletedObjects", "displayName": "Deleted Objects", "shape": "number" }, + { "name": "apiCalls", "displayName": "API Calls", "shape": "number" }, + { "name": "egress", "displayName": "Egress", "shape": "number" }, + { "name": "ingress", "displayName": "Ingress", "shape": "number" }, + { "pattern": ".*" } + ], + "timeframes": [ + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "lastQuarter", + "thisYear", + "lastYear" + ] +} diff --git a/plugins/WasabiWACM/v1/dataStreams/controlAccountSummary.json b/plugins/WasabiWACM/v1/dataStreams/controlAccountSummary.json new file mode 100644 index 00000000..46906068 --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/controlAccountSummary.json @@ -0,0 +1,41 @@ +{ + "name": "controlAccountSummary", + "displayName": "Control Account Summary", + "description": "Current state and storage of a control account", + "tags": ["Accounts"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/control-accounts/{{object.rawId}}", + "pathToData": "data" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Wasabi Control Account"] } }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "role": "id", "visible": false }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { + "name": "status", + "displayName": "Status", + "shape": [ + "state", + { + "map": { + "success": ["Active"], + "error": ["Deactivated"] + } + } + ] + }, + { "name": "subAccountStorage", "displayName": "Sub-Account Storage", "shape": "number" }, + { "name": "controlAccountStorage", "displayName": "Control Account Storage", "shape": "number" }, + { "name": "totalStorage", "displayName": "Total Storage", "shape": "number" }, + { "name": "defaultPurchasedStorageTB", "displayName": "Default Purchased Storage (TB)", "shape": "number" }, + { "name": "subAccountsCount", "displayName": "Sub-Accounts Count", "shape": "number" }, + { "name": "channelAccountsCount", "displayName": "Channel Accounts Count", "shape": "number" }, + { "name": "storageQuotaType", "displayName": "Storage Quota Type", "shape": "string" }, + { "name": "primaryApiKey", "visible": false }, + { "name": "secondaryApiKey", "visible": false }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/WasabiWACM/v1/dataStreams/controlAccountUsageHistory.json b/plugins/WasabiWACM/v1/dataStreams/controlAccountUsageHistory.json new file mode 100644 index 00000000..03d14480 --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/controlAccountUsageHistory.json @@ -0,0 +1,52 @@ +{ + "name": "controlAccountUsageHistory", + "displayName": "Control Account Usage History", + "description": "Periodic storage and traffic usage records for a control account", + "tags": ["Accounts", "Usage"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/control-accounts/usages", + "getArgs": [ + { "key": "controlAccountId", "value": "{{object.rawId}}" }, + { "key": "from", "value": "{{timeframe.start.split('T')[0]}}" }, + { "key": "to", "value": "{{timeframe.end.split('T')[0]}}" } + ], + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Wasabi Control Account"] } }, + "metadata": [ + { "name": "startTime", "displayName": "Start Time", "shape": "date", "role": "timestamp" }, + { "name": "endTime", "displayName": "End Time", "shape": "date" }, + { "name": "activeStorage", "displayName": "Active Storage (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "deletedStorage", "displayName": "Deleted Storage (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "storageWrote", "displayName": "Storage Written (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "storageRead", "displayName": "Storage Read (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "activeObjects", "displayName": "Active Objects", "shape": "number" }, + { "name": "deletedObjects", "displayName": "Deleted Objects", "shape": "number" }, + { "name": "apiCalls", "displayName": "API Calls", "shape": "number" }, + { "name": "egress", "displayName": "Egress (GB)", "shape": "number" }, + { "name": "ingress", "displayName": "Ingress (GB)", "shape": "number" }, + { "pattern": ".*" } + ], + "timeframes": [ + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "lastQuarter", + "thisYear", + "lastYear" + ] +} diff --git a/plugins/WasabiWACM/v1/dataStreams/controlAccounts.json b/plugins/WasabiWACM/v1/dataStreams/controlAccounts.json new file mode 100644 index 00000000..09092e8e --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/controlAccounts.json @@ -0,0 +1,30 @@ +{ + "name": "controlAccounts", + "displayName": "Control Accounts", + "description": "All Wasabi Control Accounts, one row per account", + "tags": ["Accounts"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/control-accounts", + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": "none", + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "role": "id" }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/WasabiWACM/v1/dataStreams/members.json b/plugins/WasabiWACM/v1/dataStreams/members.json new file mode 100644 index 00000000..46b62bef --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/members.json @@ -0,0 +1,36 @@ +{ + "name": "members", + "displayName": "Members", + "description": "All Wasabi Sub-Account users, one row per member", + "tags": ["Accounts", "Users"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/members", + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": "none", + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "role": "id" }, + { + "name": "name", + "displayName": "Name", + "computed": true, + "valueExpression": "{{ ((($['firstName'] || '') + ' ' + ($['lastName'] || '')).trim()) || $['username'] }}", + "role": "label" + }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccountBucketUtilization.json b/plugins/WasabiWACM/v1/dataStreams/subAccountBucketUtilization.json new file mode 100644 index 00000000..42a99832 --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/subAccountBucketUtilization.json @@ -0,0 +1,52 @@ +{ + "name": "subAccountBucketUtilization", + "displayName": "Sub-Account Bucket Utilization", + "description": "Per-bucket storage utilization for a sub-account", + "tags": ["Accounts", "Usage"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/sub-accounts/{{object.rawId}}/buckets", + "getArgs": [ + { "key": "from", "value": "{{timeframe.start.split('T')[0]}}" }, + { "key": "to", "value": "{{timeframe.end.split('T')[0]}}" } + ], + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Wasabi Sub-Account"] } }, + "metadata": [ + { "name": "name", "displayName": "Bucket Name", "shape": "string", "role": "label" }, + { "name": "region", "displayName": "Region", "shape": "string" }, + { "name": "bucketNumber", "displayName": "Bucket Number", "shape": "string" }, + { "name": "startTime", "displayName": "Start Time", "shape": "date", "role": "timestamp" }, + { "name": "endTime", "displayName": "End Time", "shape": "date" }, + { "name": "activeStorage", "displayName": "Active Storage", "shape": "number" }, + { "name": "deletedStorage", "displayName": "Deleted Storage", "shape": "number" }, + { "name": "activeObjects", "displayName": "Active Objects", "shape": "number" }, + { "name": "deletedObjects", "displayName": "Deleted Objects", "shape": "number" }, + { "name": "apiCalls", "displayName": "API Calls", "shape": "number" }, + { "name": "egress", "displayName": "Egress", "shape": "number" }, + { "name": "ingress", "displayName": "Ingress", "shape": "number" }, + { "pattern": ".*" } + ], + "timeframes": [ + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "lastQuarter", + "thisYear", + "lastYear" + ] +} diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccountInvoices.json b/plugins/WasabiWACM/v1/dataStreams/subAccountInvoices.json new file mode 100644 index 00000000..fdfd6fce --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/subAccountInvoices.json @@ -0,0 +1,174 @@ +{ + "name": "subAccountInvoices", + "displayName": "Sub-Account Invoices", + "description": "Billed cost line items for a sub-account", + "tags": [ + "Accounts", + "Cost" + ], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/invoices", + "getArgs": [ + { + "key": "subAccountId", + "value": "{{object.rawId}}" + }, + { + "key": "from", + "value": "{{timeframe.start.split('T')[0]}}" + }, + { + "key": "to", + "value": "{{timeframe.end.split('T')[0]}}" + } + ], + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { + "realm": "queryArg", + "path": "size", + "value": "100" + }, + "offset": { + "mode": "page", + "rowCountIn": { + "realm": "payloadArraySize", + "path": "data.items" + }, + "base": 0 + }, + "out": { + "realm": "queryArg", + "path": "page" + } + } + }, + "matches": { + "sourceType": { + "type": "oneOf", + "values": [ + "Wasabi Sub-Account" + ] + } + }, + "metadata": [ + { + "name": "periodStart", + "displayName": "Period Start", + "shape": "date", + "role": "timestamp" + }, + { + "name": "periodEnd", + "displayName": "Period End", + "shape": "date" + }, + { + "name": "totalStorage", + "displayName": "Total Storage", + "shape": "number" + }, + { + "name": "activeStorage", + "displayName": "Active Storage", + "shape": "number" + }, + { + "name": "deletedStorage", + "displayName": "Deleted Storage", + "shape": "number" + }, + { + "name": "activeStorageTotalCost", + "displayName": "Active Storage Cost", + "shape": [ + "currency", + { + "decimalPlaces": 2, + "code": "usd", + "thousandsSeparator": true + } + ] + }, + { + "name": "deletedStorageTotalCost", + "displayName": "Deleted Storage Cost", + "shape": [ + "currency", + { + "decimalPlaces": 2, + "code": "usd", + "thousandsSeparator": true + } + ] + }, + { + "name": "apiCallsTotalCost", + "displayName": "API Calls Cost", + "shape": [ + "currency", + { + "decimalPlaces": 2, + "code": "usd", + "thousandsSeparator": true + } + ] + }, + { + "name": "ingressTotalCost", + "displayName": "Ingress Cost", + "shape": [ + "currency", + { + "decimalPlaces": 2, + "code": "usd", + "thousandsSeparator": true + } + ] + }, + { + "name": "egressTotalCost", + "displayName": "Egress Cost", + "shape": [ + "currency", + { + "decimalPlaces": 2, + "code": "usd", + "thousandsSeparator": true + } + ] + }, + { + "name": "minimumActiveStorageTotalCost", + "displayName": "Minimum Active Storage Cost", + "shape": [ + "currency", + { + "decimalPlaces": 2, + "code": "usd", + "thousandsSeparator": true + } + ] + }, + { + "name": "subInvoiceId", + "displayName": "Sub-Invoice ID", + "shape": "string" + }, + { + "pattern": ".*" + } + ], + "timeframes": [ + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "lastQuarter", + "thisYear", + "lastYear" + ] +} \ No newline at end of file diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccountSummary.json b/plugins/WasabiWACM/v1/dataStreams/subAccountSummary.json new file mode 100644 index 00000000..cf693d8a --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/subAccountSummary.json @@ -0,0 +1,39 @@ +{ + "name": "subAccountSummary", + "displayName": "Sub-Account Summary", + "description": "Current state and quota of a sub-account", + "tags": ["Accounts"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/sub-accounts/{{object.rawId}}", + "pathToData": "data" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Wasabi Sub-Account"] } }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "role": "id", "visible": false }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { + "name": "status", + "displayName": "Status", + "shape": [ + "state", + { + "map": { + "success": ["PAID_ACCOUNT", "ON_TRIAL"], + "warning": ["SUSPENDED"], + "error": ["DEACTIVATED"] + } + } + ] + }, + { "name": "activeStorage", "displayName": "Active Storage", "shape": "number" }, + { "name": "deletedStorage", "displayName": "Deleted Storage", "shape": "number" }, + { "name": "purchasedStorageTB", "displayName": "Purchased Storage (TB)", "shape": "number" }, + { "name": "trialQuotaTB", "displayName": "Trial Quota (TB)", "shape": "number" }, + { "name": "accessKey", "visible": false }, + { "name": "secretKey", "visible": false }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccountUsageHistory.json b/plugins/WasabiWACM/v1/dataStreams/subAccountUsageHistory.json new file mode 100644 index 00000000..ad54ecfe --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/subAccountUsageHistory.json @@ -0,0 +1,52 @@ +{ + "name": "subAccountUsageHistory", + "displayName": "Sub-Account Usage History", + "description": "Periodic storage and traffic usage records for a sub-account", + "tags": ["Accounts", "Usage"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/usages", + "getArgs": [ + { "key": "subAccountId", "value": "{{object.rawId}}" }, + { "key": "from", "value": "{{timeframe.start.split('T')[0]}}" }, + { "key": "to", "value": "{{timeframe.end.split('T')[0]}}" } + ], + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Wasabi Sub-Account"] } }, + "metadata": [ + { "name": "startTime", "displayName": "Start Time", "shape": "date", "role": "timestamp" }, + { "name": "endTime", "displayName": "End Time", "shape": "date" }, + { "name": "activeStorage", "displayName": "Active Storage (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "deletedStorage", "displayName": "Deleted Storage (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "storageWrote", "displayName": "Storage Written (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "storageRead", "displayName": "Storage Read (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "activeObjects", "displayName": "Active Objects", "shape": "number" }, + { "name": "deletedObjects", "displayName": "Deleted Objects", "shape": "number" }, + { "name": "apiCalls", "displayName": "API Calls", "shape": "number" }, + { "name": "egress", "displayName": "Egress (GB)", "shape": "number" }, + { "name": "ingress", "displayName": "Ingress (GB)", "shape": "number" }, + { "pattern": ".*" } + ], + "timeframes": [ + "last7days", + "last30days", + "thisMonth", + "lastMonth", + "thisQuarter", + "lastQuarter", + "thisYear", + "lastYear" + ] +} diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccounts.json b/plugins/WasabiWACM/v1/dataStreams/subAccounts.json new file mode 100644 index 00000000..da8157f6 --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/subAccounts.json @@ -0,0 +1,32 @@ +{ + "name": "subAccounts", + "displayName": "Sub-Accounts", + "description": "All Wasabi Sub-Accounts, one row per account", + "tags": ["Accounts"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/sub-accounts", + "pathToData": "data.items", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, + "offset": { + "mode": "page", + "rowCountIn": { "realm": "payloadArraySize", "path": "data.items" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "page" } + } + }, + "matches": "none", + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "id", "displayName": "ID", "shape": "string", "role": "id" }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "name": "accessKey", "visible": false }, + { "name": "secretKey", "visible": false }, + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/WasabiWACM/v1/dataStreams/wacmConfigValidation.json b/plugins/WasabiWACM/v1/dataStreams/wacmConfigValidation.json new file mode 100644 index 00000000..5b407e6b --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/wacmConfigValidation.json @@ -0,0 +1,15 @@ +{ + "name": "wacmConfigValidation", + "displayName": "WACM Config Validation", + "description": "Reference list of supported storage amount tiers, used to confirm credentials are valid", + "tags": ["Reference"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "/api/v1/sub-accounts/" + }, + "matches": "none", + "visibility": { "type": "hidden" }, + "metadata": [{ "pattern": ".*" }], + "timeframes": false +} diff --git a/plugins/WasabiWACM/v1/defaultContent/channelAccountDashboard.dash.json b/plugins/WasabiWACM/v1/defaultContent/channelAccountDashboard.dash.json new file mode 100644 index 00000000..9dfabe8d --- /dev/null +++ b/plugins/WasabiWACM/v1/defaultContent/channelAccountDashboard.dash.json @@ -0,0 +1,49 @@ +{ + "name": "Channel Account", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": ["{{variables.[Channel Account]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "05c49dbb-fe02-4afa-94fc-72c40ff22864", + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Channel Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Channel Account]}}" + }, + "variables": ["{{variables.[Channel Account]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "hiddenColumns": ["id", "links", "type", "label", "sourceId", "sourceType"] + } + } + } + } + } + ] + } +} diff --git a/plugins/WasabiWACM/v1/defaultContent/controlAccountDashboard.dash.json b/plugins/WasabiWACM/v1/defaultContent/controlAccountDashboard.dash.json new file mode 100644 index 00000000..c40f48ee --- /dev/null +++ b/plugins/WasabiWACM/v1/defaultContent/controlAccountDashboard.dash.json @@ -0,0 +1,299 @@ +{ + "name": "Control Account", + "schemaVersion": "1.5", + "timeframe": "last30days", + "variables": ["{{variables.[Control Account]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "b9ee9587-3c24-40d5-ad5d-e3f3b66cd077", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Status", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[controlAccountSummary]}}", + "name": "controlAccountSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Control Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Control Account]}}" + }, + "variables": ["{{variables.[Control Account]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "status", + "comparisonColumn": "none", + "label": "Status" + } + } + } + } + }, + { + "i": "765a8f8e-f694-4f78-a740-1a18770da6da", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Storage", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[controlAccountSummary]}}", + "name": "controlAccountSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Control Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Control Account]}}" + }, + "variables": ["{{variables.[Control Account]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "totalStorage", + "comparisonColumn": "none", + "label": "Total Storage" + } + } + } + } + }, + { + "i": "092d2f44-0f35-43cf-8376-d9566be6c82b", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Sub-Accounts Count", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[controlAccountSummary]}}", + "name": "controlAccountSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Control Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Control Account]}}" + }, + "variables": ["{{variables.[Control Account]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "subAccountsCount", + "comparisonColumn": "none", + "label": "Sub-Accounts Count" + } + } + } + } + }, + { + "i": "fc5ad0e6-37cf-422e-a082-02b077ee6b22", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Channel Accounts Count", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[controlAccountSummary]}}", + "name": "controlAccountSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Control Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Control Account]}}" + }, + "variables": ["{{variables.[Control Account]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "channelAccountsCount", + "comparisonColumn": "none", + "label": "Channel Accounts Count" + } + } + } + } + }, + { + "i": "a9e851a7-366f-48fc-8f04-4aa924bfc8fb", + "x": 0, + "y": 2, + "w": 1, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Control Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Control Account]}}" + }, + "variables": ["{{variables.[Control Account]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "hiddenColumns": ["id", "links", "type", "label", "sourceId", "sourceType"] + } + } + } + } + }, + { + "i": "1e9b4d24-142a-4e76-ac14-59b1c6ace958", + "x": 1, + "y": 2, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Usage History", + "description": "Daily storage usage over the dashboard timeframe", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[controlAccountUsageHistory]}}", + "name": "controlAccountUsageHistory", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["startTime", "byDay"]], + "aggregate": [ + { "type": "sum", "names": ["activeStorage"] }, + { "type": "sum", "names": ["deletedStorage"] } + ] + }, + "sort": { "by": [["startTime_byDay", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[Control Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Control Account]}}" + }, + "variables": ["{{variables.[Control Account]}}"], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "startTime_byDay", + "yAxisColumn": ["activeStorage_sum", "deletedStorage_sum"], + "seriesColumn": "none", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "Storage (TB)", + "showYAxisLabel": true, + "showTrendLine": false + } + } + } + } + }, + { + "i": "8e648227-c704-4c97-b58d-8ef53fe52a01", + "x": 0, + "y": 5, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Bucket Utilization", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[controlAccountBucketUtilization]}}", + "name": "controlAccountBucketUtilization", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["activeStorage", "desc"]] } + }, + "scope": { + "scope": "{{scopes.[Control Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Control Account]}}" + }, + "variables": ["{{variables.[Control Account]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "region", + "activeStorage", + "deletedStorage", + "activeObjects", + "deletedObjects", + "apiCalls", + "egress", + "ingress" + ], + "hiddenColumns": ["bucketNumber", "startTime", "endTime"] + } + } + } + } + } + ] + } +} diff --git a/plugins/WasabiWACM/v1/defaultContent/manifest.json b/plugins/WasabiWACM/v1/defaultContent/manifest.json new file mode 100644 index 00000000..6c9986f4 --- /dev/null +++ b/plugins/WasabiWACM/v1/defaultContent/manifest.json @@ -0,0 +1,9 @@ +{ + "items": [ + { "name": "overview", "type": "dashboard" }, + { "name": "controlAccountDashboard", "type": "dashboard" }, + { "name": "channelAccountDashboard", "type": "dashboard" }, + { "name": "subAccountDashboard", "type": "dashboard" }, + { "name": "standaloneAccountDashboard", "type": "dashboard" } + ] +} diff --git a/plugins/WasabiWACM/v1/defaultContent/overview.dash.json b/plugins/WasabiWACM/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..666ac56e --- /dev/null +++ b/plugins/WasabiWACM/v1/defaultContent/overview.dash.json @@ -0,0 +1,291 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "none", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "7ae96803-bf20-4b4e-a55b-8b601f364657", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Sub-Accounts", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "group": { "by": [], "aggregate": [{ "type": "count" }] } + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Total Sub-Accounts" + } + } + } + } + }, + { + "i": "e7bbb607-ca23-4d04-8dd7-122ba6841c51", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Active Storage", + "description": "Sum of active storage across all sub-accounts", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "group": { "by": [], "aggregate": [{ "type": "sum", "names": ["activeStorage"] }] } + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "activeStorage_sum", + "comparisonColumn": "none", + "label": "Total Active Storage" + } + } + } + } + }, + { + "i": "0e5bb039-c7bc-4dbd-a10e-4fed0213d2db", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Control Accounts", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "group": { "by": [], "aggregate": [{ "type": "count" }] } + }, + "scope": { + "scope": "{{scopes.[Control Accounts]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Total Control Accounts" + } + } + } + } + }, + { + "i": "841b58b8-c4e5-4dce-8f39-2dde1f854e01", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Channel Accounts", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "group": { "by": [], "aggregate": [{ "type": "count" }] } + }, + "scope": { + "scope": "{{scopes.[Channel Accounts]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Total Channel Accounts" + } + } + } + } + }, + { + "i": "4b6099cd-5684-459e-800f-ec18b11145ba", + "x": 0, + "y": 2, + "w": 1, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Sub-Accounts by Status", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "group": { + "by": [["status", "uniqueValues"]], + "aggregate": [{ "type": "count" }] + } + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "status_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table" + } + } + } + } + }, + { + "i": "8bb333eb-ef71-47a3-bb4c-4dba3c16976e", + "x": 1, + "y": 2, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top 10 Sub-Accounts by Active Storage", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "sort": { "by": [["activeStorage", "desc"]], "top": 10 } + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "name", + "yAxisData": ["activeStorage"], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Active Storage", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { "type": "auto" } + } + } + } + } + }, + { + "i": "d009ed2f-31b2-4965-b9d1-16c43574770e", + "x": 0, + "y": 5, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "All Sub-Accounts", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "sort": { "by": [["activeStorage", "desc"]] } + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "status", + "activeStorage", + "deletedStorage", + "purchasedStorageTB", + "controlAccountName", + "channelAccountName" + ], + "hiddenColumns": ["id", "links", "type", "label", "sourceId", "sourceType"] + } + } + } + } + } + ] + } +} diff --git a/plugins/WasabiWACM/v1/defaultContent/scopes.json b/plugins/WasabiWACM/v1/defaultContent/scopes.json new file mode 100644 index 00000000..18bd4f30 --- /dev/null +++ b/plugins/WasabiWACM/v1/defaultContent/scopes.json @@ -0,0 +1,50 @@ +[ + { + "name": "Sub-Accounts", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Wasabi Sub-Account"] } + }, + "variable": { + "name": "Sub-Account", + "type": "object", + "default": "none", + "allowMultipleSelection": false + } + }, + { + "name": "Control Accounts", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Wasabi Control Account"] } + }, + "variable": { + "name": "Control Account", + "type": "object", + "default": "none", + "allowMultipleSelection": false + } + }, + { + "name": "Channel Accounts", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Wasabi Channel Account"] } + }, + "variable": { + "name": "Channel Account", + "type": "object", + "default": "none", + "allowMultipleSelection": false + } + }, + { + "name": "Standalone Accounts", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Wasabi Standalone Account"] } + }, + "variable": { + "name": "Standalone Account", + "type": "object", + "default": "none", + "allowMultipleSelection": false + } + } +] diff --git a/plugins/WasabiWACM/v1/defaultContent/standaloneAccountDashboard.dash.json b/plugins/WasabiWACM/v1/defaultContent/standaloneAccountDashboard.dash.json new file mode 100644 index 00000000..d6a38867 --- /dev/null +++ b/plugins/WasabiWACM/v1/defaultContent/standaloneAccountDashboard.dash.json @@ -0,0 +1,49 @@ +{ + "name": "Standalone Account", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": ["{{variables.[Standalone Account]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "69bb9842-b955-45c4-aa6a-1f4bdad2e2ad", + "x": 0, + "y": 0, + "w": 4, + "h": 4, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Standalone Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Standalone Account]}}" + }, + "variables": ["{{variables.[Standalone Account]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "hiddenColumns": ["id", "links", "type", "label", "sourceId", "sourceType"] + } + } + } + } + } + ] + } +} diff --git a/plugins/WasabiWACM/v1/defaultContent/subAccountDashboard.dash.json b/plugins/WasabiWACM/v1/defaultContent/subAccountDashboard.dash.json new file mode 100644 index 00000000..d8324603 --- /dev/null +++ b/plugins/WasabiWACM/v1/defaultContent/subAccountDashboard.dash.json @@ -0,0 +1,344 @@ +{ + "name": "Sub-Account", + "schemaVersion": "1.5", + "timeframe": "last30days", + "variables": ["{{variables.[Sub-Account]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "6077a6b9-92ca-4b3a-b3e4-245ddc593596", + "x": 0, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Status", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[subAccountSummary]}}", + "name": "subAccountSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Sub-Account]}}" + }, + "variables": ["{{variables.[Sub-Account]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "status", + "comparisonColumn": "none", + "label": "Status" + } + } + } + } + }, + { + "i": "c296d94b-458c-47dd-b85d-d6ff6bb4022e", + "x": 1, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Active Storage", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[subAccountSummary]}}", + "name": "subAccountSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Sub-Account]}}" + }, + "variables": ["{{variables.[Sub-Account]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "activeStorage", + "comparisonColumn": "none", + "label": "Active Storage" + } + } + } + } + }, + { + "i": "6a8461bb-722d-4fb6-828b-81d130ad95f5", + "x": 2, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Purchased Storage (TB)", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[subAccountSummary]}}", + "name": "subAccountSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Sub-Account]}}" + }, + "variables": ["{{variables.[Sub-Account]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "purchasedStorageTB", + "comparisonColumn": "none", + "label": "Purchased Storage (TB)" + } + } + } + } + }, + { + "i": "df489526-1623-490a-a5a5-f9d27471a1b3", + "x": 3, + "y": 0, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "MFA Enabled", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[subAccountSummary]}}", + "name": "subAccountSummary", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Sub-Account]}}" + }, + "variables": ["{{variables.[Sub-Account]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "mfaEnabled", + "comparisonColumn": "none", + "label": "MFA Enabled" + } + } + } + } + }, + { + "i": "b93c6b90-4a2d-4c01-97c6-06cb509bf8c0", + "x": 0, + "y": 2, + "w": 1, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Properties", + "description": "", + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Sub-Account]}}" + }, + "variables": ["{{variables.[Sub-Account]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "hiddenColumns": ["id", "links", "type", "label", "sourceId", "sourceType"] + } + } + } + } + }, + { + "i": "4cd92bb1-d28b-4730-aded-3472dc7bc2a1", + "x": 1, + "y": 2, + "w": 3, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Usage History", + "description": "Daily storage usage over the dashboard timeframe", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[subAccountUsageHistory]}}", + "name": "subAccountUsageHistory", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["startTime", "byDay"]], + "aggregate": [ + { "type": "sum", "names": ["activeStorage"] }, + { "type": "sum", "names": ["deletedStorage"] } + ] + }, + "sort": { "by": [["startTime_byDay", "asc"]] } + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Sub-Account]}}" + }, + "variables": ["{{variables.[Sub-Account]}}"], + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "startTime_byDay", + "yAxisColumn": ["activeStorage_sum", "deletedStorage_sum"], + "seriesColumn": "none", + "showLegend": true, + "legendPosition": "bottom", + "yAxisLabel": "Storage (TB)", + "showYAxisLabel": true, + "showTrendLine": false + } + } + } + } + }, + { + "i": "dc8eeb8c-0538-409e-b7ab-5a2f96ff5b26", + "x": 0, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Bucket Utilization", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[subAccountBucketUtilization]}}", + "name": "subAccountBucketUtilization", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["activeStorage", "desc"]] } + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Sub-Account]}}" + }, + "variables": ["{{variables.[Sub-Account]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "name", + "region", + "activeStorage", + "deletedStorage", + "activeObjects", + "apiCalls", + "egress", + "ingress" + ], + "hiddenColumns": ["bucketNumber", "startTime", "endTime", "deletedObjects"] + } + } + } + } + }, + { + "i": "75641d05-970b-4fee-b43b-3b347a74cf82", + "x": 2, + "y": 5, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Invoices", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[subAccountInvoices]}}", + "name": "subAccountInvoices", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["periodStart", "desc"]] } + }, + "scope": { + "scope": "{{scopes.[Sub-Accounts]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Sub-Account]}}" + }, + "variables": ["{{variables.[Sub-Account]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": [ + "periodStart", + "periodEnd", + "activeStorageTotalCost", + "deletedStorageTotalCost", + "apiCallsTotalCost", + "ingressTotalCost", + "egressTotalCost" + ], + "hiddenColumns": ["subInvoiceId", "minimumActiveStorageTotalCost", "totalStorage", "activeStorage", "deletedStorage"] + } + } + } + } + } + ] + } +} diff --git a/plugins/WasabiWACM/v1/docs/README.md b/plugins/WasabiWACM/v1/docs/README.md new file mode 100644 index 00000000..08d79862 --- /dev/null +++ b/plugins/WasabiWACM/v1/docs/README.md @@ -0,0 +1,61 @@ +Monitor your [Wasabi](https://wasabi.com) reseller and multi-tenant account hierarchy in SquaredUp — Control Accounts, Channel Accounts, Sub-Accounts, storage usage, bucket utilization, and billing — via the [WACM Connect API](https://docs.wasabi.com/apidocs/wacm-connect-api). + +> ⚠️ This plugin covers the **WACM (Wasabi Account Control Manager)** account-management API only, used by Wasabi partners/resellers to manage downstream accounts. It does not cover Wasabi's S3-compatible storage API (buckets/objects data plane) — only account-level storage and billing metrics reported by WACM. + +## Setup + +You will need a WACM **API key**, paired with the corresponding account name or username as its "username". + +1. Sign in to your [Wasabi Account Control Manager console](https://console.wacm.wasabisys.com). +2. To use an **Account API Key** (recommended for Control/Channel/Sub-Account-wide access): go to the **Account** tab and generate an API key as the Account Admin. Note the **account name** shown alongside it. +3. Alternatively, to use a **User API Key** (scoped to what that user can see): open your own profile and generate a **User API key**. Note your **WACM username**. +4. Copy the key — it is shown only once — and paste it, along with the matching account name or username, into the fields below. + +> ⚠️ Regenerating an API key permanently invalidates the old one — update this plugin's configuration if you rotate keys. + +## Configuration fields + +| Field | What it is | Where to find it | Required | +| ----- | ---------- | ----------------- | -------- | +| **Username** | The WACM account name (for an Account API Key) or WACM username (for a User API Key). | WACM console → **Account** tab, or your user profile. | Yes | +| **API Key** | Authenticates every request via HTTP Basic Auth. | WACM console → **Account** tab (Account API Key) or your user profile (User API key). | Yes | + +On save, the plugin validates the credentials by calling a lightweight reference endpoint; invalid credentials fail setup with an authentication error. + +## What this plugin monitors + +- **Account hierarchy** — Standalone Accounts, Control Accounts, Channel Accounts, Sub-Accounts, and Members, with each object's key properties (status, storage quota, contact details). +- **Storage usage** — daily active/deleted storage, object counts, and traffic (egress/ingress/API calls) history per Sub-Account and Control Account. +- **Bucket utilization** — daily per-bucket storage breakdowns per Sub-Account and Control Account. +- **Billing** — monthly invoice line items and costs per Sub-Account. + +The out-of-the-box dashboards include an estate-wide **Overview** plus a perspective for each of **Control Account**, **Channel Account**, **Sub-Account**, and **Standalone Account**. + +## Data streams + +- **Sub-Account Summary** — current status and storage quota, one row per sub-account. +- **Sub-Account Usage History** — daily storage and traffic usage history for a sub-account. +- **Sub-Account Bucket Utilization** — daily per-bucket storage utilization for a sub-account. +- **Sub-Account Invoices** — monthly billed cost line items for a sub-account. +- **Control Account Summary** — current status and storage totals, one row per control account. +- **Control Account Usage History** — daily storage and traffic usage history for a control account. +- **Control Account Bucket Utilization** — daily per-bucket storage utilization for a control account. + +## What gets indexed + +| Object type | API source | Represents | +| ----------- | ---------- | ---------- | +| **Wasabi Standalone Account** | `GET /api/v1/accounts` | An independent Wasabi console account outside the reseller hierarchy. | +| **Wasabi Control Account** | `GET /api/v1/control-accounts` | The top-level billing-owning account in a reseller hierarchy. | +| **Wasabi Channel Account** | `GET /api/v1/channel-accounts` | An optional reseller/partner access layer under a Control Account. | +| **Wasabi Sub-Account** | `GET /api/v1/sub-accounts` | An actual Wasabi Console account — the entity that owns buckets and is billed. | +| **Wasabi Member** | `GET /api/v1/members` | A user with access to a Sub-Account. | + +## Known limitations + +- **Read-only** — this plugin never creates, modifies, or deletes anything in Wasabi WACM. +- **Permission tiers vary by API key.** The WACM API scopes each endpoint to the calling account's tier. A **Sub-Account**-tier key (the most common case) can see its own Sub-Accounts, Channel Accounts, and Members, but **Standalone Accounts and Control Accounts will show no indexed objects** — and their dashboards will stay empty — until a Control-Account-tier or Account-Admin key is used. +- **Usage and bucket utilization data is daily-granularity** — timeframes shorter than 7 days aren't offered. +- **Invoices are monthly billing-period records** — timeframes shorter than 30 days aren't offered, and a newly created sub-account may show no invoices until its first billing cycle completes. +- **Members aren't filtered to a specific Sub-Account** in dashboards — indexed Members can be browsed as their own object type, but no per-sub-account member table is shown on the Sub-Account perspective. +- **Channel Account and Standalone Account perspectives show only indexed properties** — the WACM API doesn't expose dedicated usage or billing endpoints for these tiers. diff --git a/plugins/WasabiWACM/v1/icon.svg b/plugins/WasabiWACM/v1/icon.svg new file mode 100644 index 00000000..a1ed4da7 --- /dev/null +++ b/plugins/WasabiWACM/v1/icon.svg @@ -0,0 +1,7 @@ + +Wasabi + + + + + diff --git a/plugins/WasabiWACM/v1/indexDefinitions/default.json b/plugins/WasabiWACM/v1/indexDefinitions/default.json new file mode 100644 index 00000000..af77323b --- /dev/null +++ b/plugins/WasabiWACM/v1/indexDefinitions/default.json @@ -0,0 +1,160 @@ +{ + "steps": [ + { + "name": "accounts", + "dataStream": { "name": "accounts" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Wasabi Standalone Account" }, + "properties": [ + "firstName", + "lastName", + "companyName", + "email", + "country", + "phoneNumber", + "partnerName", + "wasabiAccountNumber", + "status", + "storageAmount" + ] + }, + "optional": true + }, + { + "name": "controlAccounts", + "dataStream": { "name": "controlAccounts" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Wasabi Control Account" }, + "properties": [ + "partnerType", + "accountType", + "status", + "creationDate", + "country", + "city", + "state", + "contactEmail", + "billingEmail", + "governanceAccountId", + "governanceAccountName", + "channelAccountsCount", + "subAccountsCount", + "controlAccountEmail", + "subAccountStorage", + "controlAccountStorage", + "totalStorage", + "defaultPurchasedStorageTB", + "storageQuotaType", + "ssoEnabled" + ] + }, + "optional": true + }, + { + "name": "channelAccounts", + "dataStream": { "name": "channelAccounts" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Wasabi Channel Account" }, + "properties": [ + "controlAccountId", + "controlAccountName", + "governanceAccountId", + "governanceAccountName", + "purchasedStorage", + "subAccountDefaultPurchasedStorage", + "subAccountDefaultStorageQuotaType", + "storageQuotaType", + "subAccountStorage", + "billableActiveStorage", + "billableDeletedStorage", + "subAccountsCount", + "status", + "creationDate", + "country", + "city", + "state", + "contactEmail", + "billingEmail", + "deleted" + ] + }, + "optional": true + }, + { + "name": "subAccounts", + "dataStream": { "name": "subAccounts" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Wasabi Sub-Account" }, + "properties": [ + "partnerType", + "accountType", + "status", + "creationDate", + "country", + "city", + "state", + "contactEmail", + "billingEmail", + "channelAccountId", + "channelAccountName", + "channelAccountEmail", + "controlAccountId", + "controlAccountName", + "controlAccountEmail", + "governanceAccountId", + "governanceAccountName", + "wasabiAccountNumber", + "wasabiAccountName", + "ftpEnabled", + "activeStorage", + "deletedStorage", + "trialQuotaTB", + "trialExpiration", + "purchasedStorageTB", + "mfaEnabled", + "storageQuotaType", + "ssoEnabled" + ] + }, + "optional": true + }, + { + "name": "members", + "dataStream": { "name": "members" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Wasabi Member" }, + "properties": [ + "subAccountId", + "firstName", + "lastName", + "username", + "memberRole", + "email", + "status", + "mfa", + "creationDate", + "country", + "city", + "phone", + "isSsoUser" + ] + }, + "optional": true + } + ] +} diff --git a/plugins/WasabiWACM/v1/metadata.json b/plugins/WasabiWACM/v1/metadata.json new file mode 100644 index 00000000..a55249ab --- /dev/null +++ b/plugins/WasabiWACM/v1/metadata.json @@ -0,0 +1,56 @@ +{ + "name": "wasabi-wacm", + "displayName": "Wasabi WACM", + "version": "1.0.0", + "author": { + "name": "@arobavet", + "type": "community" + }, + "description": "Monitor Wasabi Account Control Manager (WACM) accounts, storage usage, bucket utilization and billing across your reseller account hierarchy.", + "category": "Cloud Platforms", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": [ + "wasabi", + "wacm", + "cloud storage", + "s3", + "reseller", + "billing", + "storage", + "account management" + ], + "objectTypes": [ + "Wasabi Standalone Account", + "Wasabi Control Account", + "Wasabi Channel Account", + "Wasabi Sub-Account", + "Wasabi Member" + ], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/WasabiWACM/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/WasabiWACM/v1", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "https://api.wacm.wasabisys.com", + "authMode": "basic", + "basicAuthUsername": "{{username}}", + "basicAuthPassword": "{{apiKey}}", + "headers": [], + "queryArgs": [] + } + } +} diff --git a/plugins/WasabiWACM/v1/ui.json b/plugins/WasabiWACM/v1/ui.json new file mode 100644 index 00000000..baf5e92a --- /dev/null +++ b/plugins/WasabiWACM/v1/ui.json @@ -0,0 +1,20 @@ +[ + { + "type": "text", + "name": "username", + "label": "Username", + "help": "Your WACM account name (for an Account API Key) or WACM username (for a User API key).", + "validation": { + "required": true + } + }, + { + "type": "password", + "name": "apiKey", + "label": "API Key", + "help": "The Account API key or User API key generated in the WACM console — shown only once when created.", + "validation": { + "required": true + } + } +] From a40b98ca14e3b265fb78397500ceb9a55f76593e Mon Sep 17 00:00:00 2001 From: Teva Bouchet Date: Fri, 14 Aug 2026 13:42:56 +0200 Subject: [PATCH 2/3] Fix token expiry calculation for short-lived N-central tokens Clamps the 5-minute refresh margin below lifetimeMs so expiryTime stays in the future for short-lived tokens, while preserving early refresh for longer-lived ones. Co-Authored-By: Claude Sonnet 5 --- plugins/N-Central/v1/preRequest.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/N-Central/v1/preRequest.js b/plugins/N-Central/v1/preRequest.js index c9bf23a2..6895e719 100644 --- a/plugins/N-Central/v1/preRequest.js +++ b/plugins/N-Central/v1/preRequest.js @@ -75,13 +75,15 @@ if ( ? 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, - - // Refresh 5 minutes before expiration so a long-running request - // is less likely to fail because the token expires mid-flight. - expiryTime: Date.now() + lifetimeMs - 300000, + expiryTime: Date.now() + lifetimeMs - refreshMarginMs, }; } From 35a85998c283329e0fec85f040e34052ccc0e4ff Mon Sep 17 00:00:00 2001 From: Teva Bouchet Date: Fri, 14 Aug 2026 15:18:46 +0200 Subject: [PATCH 3/3] Address code review findings on N-Central and WasabiWACM plugins N-Central (v2.0.0 -> v2.0.1): - Limit customer/site pickers on the Devices stream to a single object - Merge deviceAssets body/_extra lists by _index instead of dropping body-only records when _extra isn't a strict superset - Fix metadata.json links pointing at the wrong plugins/NCentral folder - Correct README claims about token caching and the setup validation endpoint, and the documented minimum N-central version (2023.9+) WasabiWACM (v1.0.0 -> v1.0.1): - Label bucket utilization storage/traffic fields with their units (TB/GB) - Request includeApiKey=false / includeKeys=false on account endpoints and redact API key fields via visible:false, closing a gap where control-accounts had no key field declared at all - Convert invoice and usage-history storage/traffic fields from TB/GB to bytes and use the bytes semantic shape - Add a placeholder example to the username config field Co-Authored-By: Claude Sonnet 5 --- .../N-Central/v1/dataStreams/deviceList.json | 2 + .../v1/dataStreams/scripts/deviceAssets.js | 45 ++++++++++++++++--- plugins/N-Central/v1/docs/README.md | 6 +-- plugins/N-Central/v1/metadata.json | 6 +-- .../controlAccountBucketUtilization.json | 8 ++-- .../v1/dataStreams/controlAccountSummary.json | 1 + .../v1/dataStreams/controlAccounts.json | 3 ++ .../dataStreams/scripts/subAccountInvoices.js | 16 +++++++ .../scripts/subAccountUsageHistory.js | 20 +++++++++ .../subAccountBucketUtilization.json | 8 ++-- .../v1/dataStreams/subAccountInvoices.json | 7 +-- .../v1/dataStreams/subAccountSummary.json | 1 + .../dataStreams/subAccountUsageHistory.json | 13 +++--- .../v1/dataStreams/subAccounts.json | 1 + plugins/WasabiWACM/v1/metadata.json | 2 +- plugins/WasabiWACM/v1/ui.json | 1 + 16 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 plugins/WasabiWACM/v1/dataStreams/scripts/subAccountInvoices.js create mode 100644 plugins/WasabiWACM/v1/dataStreams/scripts/subAccountUsageHistory.js diff --git a/plugins/N-Central/v1/dataStreams/deviceList.json b/plugins/N-Central/v1/dataStreams/deviceList.json index 9edfb020..ee1dbe78 100644 --- a/plugins/N-Central/v1/dataStreams/deviceList.json +++ b/plugins/N-Central/v1/dataStreams/deviceList.json @@ -25,6 +25,7 @@ "type": "objects", "name": "customer", "label": "Customer (optional)", + "objectLimit": 1, "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Customer"] } } @@ -33,6 +34,7 @@ "type": "objects", "name": "site", "label": "Site (optional)", + "objectLimit": 1, "matches": { "sourceType": { "type": "oneOf", "values": ["N-central Site"] } } diff --git a/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js b/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js index 53886b4a..9abd4cfc 100644 --- a/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js +++ b/plugins/N-Central/v1/dataStreams/scripts/deviceAssets.js @@ -68,8 +68,11 @@ function addItem(category, item) { }, ); -// List categories: prefer "_extra"'s version when both exist (it's a strict -// superset for "application"); otherwise use whichever side has the list. +// 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), @@ -83,11 +86,39 @@ listKeys.forEach((key) => { if (skipCategories.has(key)) { return; } - const list = - extra[key] && Array.isArray(extra[key].list) - ? extra[key].list - : body[key] && body[key].list; - (list || []).forEach((item) => addItem(key, item)); + 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/docs/README.md b/plugins/N-Central/v1/docs/README.md index 5eea9921..825bc08b 100644 --- a/plugins/N-Central/v1/docs/README.md +++ b/plugins/N-Central/v1/docs/README.md @@ -1,6 +1,6 @@ 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 2022.1 or later). The legacy SOAP API is not supported by this plugin. +> ⚠️ 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 @@ -11,7 +11,7 @@ You will need your N-central server's base URL and a **User-API Token**. 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 on every request (via `POST /api/auth/authenticate`), caching and refreshing it automatically — no manual token exchange or periodic refresh needed. +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 @@ -20,7 +20,7 @@ The plugin exchanges the User-API Token for a short-lived access token itself on | **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 an authenticated system-health endpoint to confirm the User-API Token works; an invalid, unreachable, or revoked token fails setup with an authentication error. +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 diff --git a/plugins/N-Central/v1/metadata.json b/plugins/N-Central/v1/metadata.json index 744a4449..b5acfd83 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.0", + "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", @@ -19,12 +19,12 @@ "links": [ { "category": "documentation", - "url": "https://github.com/squaredup/plugins/blob/main/plugins/NCentral/v1/docs/README.md", + "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/NCentral/v1", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/N-Central/v1", "label": "Repository" } ], diff --git a/plugins/WasabiWACM/v1/dataStreams/controlAccountBucketUtilization.json b/plugins/WasabiWACM/v1/dataStreams/controlAccountBucketUtilization.json index 67f1d882..ce6805fc 100644 --- a/plugins/WasabiWACM/v1/dataStreams/controlAccountBucketUtilization.json +++ b/plugins/WasabiWACM/v1/dataStreams/controlAccountBucketUtilization.json @@ -30,13 +30,13 @@ { "name": "bucketNumber", "displayName": "Bucket Number", "shape": "string" }, { "name": "startTime", "displayName": "Start Time", "shape": "date", "role": "timestamp" }, { "name": "endTime", "displayName": "End Time", "shape": "date" }, - { "name": "activeStorage", "displayName": "Active Storage", "shape": "number" }, - { "name": "deletedStorage", "displayName": "Deleted Storage", "shape": "number" }, + { "name": "activeStorage", "displayName": "Active Storage (TB)", "shape": "number" }, + { "name": "deletedStorage", "displayName": "Deleted Storage (TB)", "shape": "number" }, { "name": "activeObjects", "displayName": "Active Objects", "shape": "number" }, { "name": "deletedObjects", "displayName": "Deleted Objects", "shape": "number" }, { "name": "apiCalls", "displayName": "API Calls", "shape": "number" }, - { "name": "egress", "displayName": "Egress", "shape": "number" }, - { "name": "ingress", "displayName": "Ingress", "shape": "number" }, + { "name": "egress", "displayName": "Egress (GB)", "shape": "number" }, + { "name": "ingress", "displayName": "Ingress (GB)", "shape": "number" }, { "pattern": ".*" } ], "timeframes": [ diff --git a/plugins/WasabiWACM/v1/dataStreams/controlAccountSummary.json b/plugins/WasabiWACM/v1/dataStreams/controlAccountSummary.json index 46906068..395f5c02 100644 --- a/plugins/WasabiWACM/v1/dataStreams/controlAccountSummary.json +++ b/plugins/WasabiWACM/v1/dataStreams/controlAccountSummary.json @@ -7,6 +7,7 @@ "config": { "httpMethod": "get", "endpointPath": "/api/v1/control-accounts/{{object.rawId}}", + "getArgs": [{ "key": "includeApiKey", "value": "false" }], "pathToData": "data" }, "matches": { "sourceType": { "type": "oneOf", "values": ["Wasabi Control Account"] } }, diff --git a/plugins/WasabiWACM/v1/dataStreams/controlAccounts.json b/plugins/WasabiWACM/v1/dataStreams/controlAccounts.json index 09092e8e..c7e6362c 100644 --- a/plugins/WasabiWACM/v1/dataStreams/controlAccounts.json +++ b/plugins/WasabiWACM/v1/dataStreams/controlAccounts.json @@ -7,6 +7,7 @@ "config": { "httpMethod": "get", "endpointPath": "/api/v1/control-accounts", + "getArgs": [{ "key": "includeApiKey", "value": "false" }], "pathToData": "data.items", "paging": { "mode": "offset", @@ -24,6 +25,8 @@ "metadata": [ { "name": "id", "displayName": "ID", "shape": "string", "role": "id" }, { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "name": "primaryApiKey", "visible": false }, + { "name": "secondaryApiKey", "visible": false }, { "pattern": ".*" } ], "timeframes": false diff --git a/plugins/WasabiWACM/v1/dataStreams/scripts/subAccountInvoices.js b/plugins/WasabiWACM/v1/dataStreams/scripts/subAccountInvoices.js new file mode 100644 index 00000000..7352f0dd --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/scripts/subAccountInvoices.js @@ -0,0 +1,16 @@ +// Wasabi reports totalStorage/activeStorage/deletedStorage in TB; convert to +// bytes to match the "bytes" shape used for these fields. +const TB_TO_BYTES = 1e12; + +function toBytes(value) { + return typeof value === "number" ? value * TB_TO_BYTES : value; +} + +const items = (data && data.data && data.data.items) || []; + +result = items.map((item) => ({ + ...item, + totalStorage: toBytes(item.totalStorage), + activeStorage: toBytes(item.activeStorage), + deletedStorage: toBytes(item.deletedStorage), +})); diff --git a/plugins/WasabiWACM/v1/dataStreams/scripts/subAccountUsageHistory.js b/plugins/WasabiWACM/v1/dataStreams/scripts/subAccountUsageHistory.js new file mode 100644 index 00000000..03289ad1 --- /dev/null +++ b/plugins/WasabiWACM/v1/dataStreams/scripts/subAccountUsageHistory.js @@ -0,0 +1,20 @@ +// Wasabi reports storage fields in TB and traffic fields in GB; convert both +// to bytes to match the "bytes" shape used for these fields. +const TB_TO_BYTES = 1e12; +const GB_TO_BYTES = 1e9; + +function scale(value, factor) { + return typeof value === "number" ? value * factor : value; +} + +const items = (data && data.data && data.data.items) || []; + +result = items.map((item) => ({ + ...item, + activeStorage: scale(item.activeStorage, TB_TO_BYTES), + deletedStorage: scale(item.deletedStorage, TB_TO_BYTES), + storageWrote: scale(item.storageWrote, TB_TO_BYTES), + storageRead: scale(item.storageRead, TB_TO_BYTES), + egress: scale(item.egress, GB_TO_BYTES), + ingress: scale(item.ingress, GB_TO_BYTES), +})); diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccountBucketUtilization.json b/plugins/WasabiWACM/v1/dataStreams/subAccountBucketUtilization.json index 42a99832..fab5b09b 100644 --- a/plugins/WasabiWACM/v1/dataStreams/subAccountBucketUtilization.json +++ b/plugins/WasabiWACM/v1/dataStreams/subAccountBucketUtilization.json @@ -30,13 +30,13 @@ { "name": "bucketNumber", "displayName": "Bucket Number", "shape": "string" }, { "name": "startTime", "displayName": "Start Time", "shape": "date", "role": "timestamp" }, { "name": "endTime", "displayName": "End Time", "shape": "date" }, - { "name": "activeStorage", "displayName": "Active Storage", "shape": "number" }, - { "name": "deletedStorage", "displayName": "Deleted Storage", "shape": "number" }, + { "name": "activeStorage", "displayName": "Active Storage (TB)", "shape": "number" }, + { "name": "deletedStorage", "displayName": "Deleted Storage (TB)", "shape": "number" }, { "name": "activeObjects", "displayName": "Active Objects", "shape": "number" }, { "name": "deletedObjects", "displayName": "Deleted Objects", "shape": "number" }, { "name": "apiCalls", "displayName": "API Calls", "shape": "number" }, - { "name": "egress", "displayName": "Egress", "shape": "number" }, - { "name": "ingress", "displayName": "Ingress", "shape": "number" }, + { "name": "egress", "displayName": "Egress (GB)", "shape": "number" }, + { "name": "ingress", "displayName": "Ingress (GB)", "shape": "number" }, { "pattern": ".*" } ], "timeframes": [ diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccountInvoices.json b/plugins/WasabiWACM/v1/dataStreams/subAccountInvoices.json index fdfd6fce..ad95ecc2 100644 --- a/plugins/WasabiWACM/v1/dataStreams/subAccountInvoices.json +++ b/plugins/WasabiWACM/v1/dataStreams/subAccountInvoices.json @@ -25,6 +25,7 @@ } ], "pathToData": "data.items", + "postRequestScript": "subAccountInvoices.js", "paging": { "mode": "offset", "pageSize": { @@ -69,17 +70,17 @@ { "name": "totalStorage", "displayName": "Total Storage", - "shape": "number" + "shape": "bytes" }, { "name": "activeStorage", "displayName": "Active Storage", - "shape": "number" + "shape": "bytes" }, { "name": "deletedStorage", "displayName": "Deleted Storage", - "shape": "number" + "shape": "bytes" }, { "name": "activeStorageTotalCost", diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccountSummary.json b/plugins/WasabiWACM/v1/dataStreams/subAccountSummary.json index cf693d8a..5b11ab1f 100644 --- a/plugins/WasabiWACM/v1/dataStreams/subAccountSummary.json +++ b/plugins/WasabiWACM/v1/dataStreams/subAccountSummary.json @@ -7,6 +7,7 @@ "config": { "httpMethod": "get", "endpointPath": "/api/v1/sub-accounts/{{object.rawId}}", + "getArgs": [{ "key": "includeKeys", "value": "false" }], "pathToData": "data" }, "matches": { "sourceType": { "type": "oneOf", "values": ["Wasabi Sub-Account"] } }, diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccountUsageHistory.json b/plugins/WasabiWACM/v1/dataStreams/subAccountUsageHistory.json index ad54ecfe..2e196202 100644 --- a/plugins/WasabiWACM/v1/dataStreams/subAccountUsageHistory.json +++ b/plugins/WasabiWACM/v1/dataStreams/subAccountUsageHistory.json @@ -13,6 +13,7 @@ { "key": "to", "value": "{{timeframe.end.split('T')[0]}}" } ], "pathToData": "data.items", + "postRequestScript": "subAccountUsageHistory.js", "paging": { "mode": "offset", "pageSize": { "realm": "queryArg", "path": "size", "value": "100" }, @@ -28,15 +29,15 @@ "metadata": [ { "name": "startTime", "displayName": "Start Time", "shape": "date", "role": "timestamp" }, { "name": "endTime", "displayName": "End Time", "shape": "date" }, - { "name": "activeStorage", "displayName": "Active Storage (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, - { "name": "deletedStorage", "displayName": "Deleted Storage (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, - { "name": "storageWrote", "displayName": "Storage Written (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, - { "name": "storageRead", "displayName": "Storage Read (TB)", "shape": ["number", { "decimalPlaces": 4 }] }, + { "name": "activeStorage", "displayName": "Active Storage", "shape": "bytes" }, + { "name": "deletedStorage", "displayName": "Deleted Storage", "shape": "bytes" }, + { "name": "storageWrote", "displayName": "Storage Written", "shape": "bytes" }, + { "name": "storageRead", "displayName": "Storage Read", "shape": "bytes" }, { "name": "activeObjects", "displayName": "Active Objects", "shape": "number" }, { "name": "deletedObjects", "displayName": "Deleted Objects", "shape": "number" }, { "name": "apiCalls", "displayName": "API Calls", "shape": "number" }, - { "name": "egress", "displayName": "Egress (GB)", "shape": "number" }, - { "name": "ingress", "displayName": "Ingress (GB)", "shape": "number" }, + { "name": "egress", "displayName": "Egress", "shape": "bytes" }, + { "name": "ingress", "displayName": "Ingress", "shape": "bytes" }, { "pattern": ".*" } ], "timeframes": [ diff --git a/plugins/WasabiWACM/v1/dataStreams/subAccounts.json b/plugins/WasabiWACM/v1/dataStreams/subAccounts.json index da8157f6..6efad6da 100644 --- a/plugins/WasabiWACM/v1/dataStreams/subAccounts.json +++ b/plugins/WasabiWACM/v1/dataStreams/subAccounts.json @@ -7,6 +7,7 @@ "config": { "httpMethod": "get", "endpointPath": "/api/v1/sub-accounts", + "getArgs": [{ "key": "includeKeys", "value": "false" }], "pathToData": "data.items", "paging": { "mode": "offset", diff --git a/plugins/WasabiWACM/v1/metadata.json b/plugins/WasabiWACM/v1/metadata.json index a55249ab..5850b149 100644 --- a/plugins/WasabiWACM/v1/metadata.json +++ b/plugins/WasabiWACM/v1/metadata.json @@ -1,7 +1,7 @@ { "name": "wasabi-wacm", "displayName": "Wasabi WACM", - "version": "1.0.0", + "version": "1.0.1", "author": { "name": "@arobavet", "type": "community" diff --git a/plugins/WasabiWACM/v1/ui.json b/plugins/WasabiWACM/v1/ui.json index baf5e92a..59ad915f 100644 --- a/plugins/WasabiWACM/v1/ui.json +++ b/plugins/WasabiWACM/v1/ui.json @@ -4,6 +4,7 @@ "name": "username", "label": "Username", "help": "Your WACM account name (for an Account API Key) or WACM username (for a User API key).", + "placeholder": "e.g. acme-msp or jane.doe", "validation": { "required": true }