From 964abbe9526c9ed0cd55a4c9b33bb0ff082f0bd5 Mon Sep 17 00:00:00 2001 From: vinbab Date: Thu, 2 Jul 2026 13:27:53 +0100 Subject: [PATCH 1/3] Add Veeam Backup & Replication plugin (v1.0.0) Low-code (Web API) plugin for Veeam Backup & Replication. Indexes backup repositories and jobs as objects, provides job/repository status and capacity data streams (all and object-scoped via idFilter), and ships an Overview plus per-object Status dashboards. Auth via OAuth2 password grant with a selectable x-api-version (Veeam 12.0-13). Co-Authored-By: Claude Opus 4.8 --- .../v1/configValidation.json | 11 + .../v1/custom_types.json | 16 + .../v1/dataStreams/jobStates.json | 45 +++ .../v1/dataStreams/jobStatesScoped.json | 40 +++ .../v1/dataStreams/repositoryStates.json | 39 +++ .../dataStreams/repositoryStatesScoped.json | 34 +++ .../v1/defaultContent/Job/manifest.json | 5 + .../v1/defaultContent/Job/status.dash.json | 199 ++++++++++++ .../defaultContent/Repository/manifest.json | 5 + .../Repository/status.dash.json | 270 +++++++++++++++++ .../v1/defaultContent/manifest.json | 7 + .../overviewDashboard.dash.json | 286 ++++++++++++++++++ .../v1/defaultContent/scopes.json | 26 ++ .../VeeamBackupReplication/v1/docs/README.md | 105 +++++++ plugins/VeeamBackupReplication/v1/icon.svg | 7 + .../v1/indexDefinitions/default.json | 34 +++ .../VeeamBackupReplication/v1/metadata.json | 51 ++++ plugins/VeeamBackupReplication/v1/ui.json | 62 ++++ 18 files changed, 1242 insertions(+) create mode 100644 plugins/VeeamBackupReplication/v1/configValidation.json create mode 100644 plugins/VeeamBackupReplication/v1/custom_types.json create mode 100644 plugins/VeeamBackupReplication/v1/dataStreams/jobStates.json create mode 100644 plugins/VeeamBackupReplication/v1/dataStreams/jobStatesScoped.json create mode 100644 plugins/VeeamBackupReplication/v1/dataStreams/repositoryStates.json create mode 100644 plugins/VeeamBackupReplication/v1/dataStreams/repositoryStatesScoped.json create mode 100644 plugins/VeeamBackupReplication/v1/defaultContent/Job/manifest.json create mode 100644 plugins/VeeamBackupReplication/v1/defaultContent/Job/status.dash.json create mode 100644 plugins/VeeamBackupReplication/v1/defaultContent/Repository/manifest.json create mode 100644 plugins/VeeamBackupReplication/v1/defaultContent/Repository/status.dash.json create mode 100644 plugins/VeeamBackupReplication/v1/defaultContent/manifest.json create mode 100644 plugins/VeeamBackupReplication/v1/defaultContent/overviewDashboard.dash.json create mode 100644 plugins/VeeamBackupReplication/v1/defaultContent/scopes.json create mode 100644 plugins/VeeamBackupReplication/v1/docs/README.md create mode 100644 plugins/VeeamBackupReplication/v1/icon.svg create mode 100644 plugins/VeeamBackupReplication/v1/indexDefinitions/default.json create mode 100644 plugins/VeeamBackupReplication/v1/metadata.json create mode 100644 plugins/VeeamBackupReplication/v1/ui.json diff --git a/plugins/VeeamBackupReplication/v1/configValidation.json b/plugins/VeeamBackupReplication/v1/configValidation.json new file mode 100644 index 00000000..90147ff2 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Connect to Veeam Backup & Replication", + "dataStream": { "name": "repositoryStates" }, + "required": true, + "success": "Successfully connected to Veeam Backup & Replication.", + "error": "Could not connect to Veeam Backup & Replication. Check the Server URL and port, the username and password, and the selected REST API version, and ensure the account has at least the Veeam Backup Viewer role." + } + ] +} diff --git a/plugins/VeeamBackupReplication/v1/custom_types.json b/plugins/VeeamBackupReplication/v1/custom_types.json new file mode 100644 index 00000000..a368a9e9 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/custom_types.json @@ -0,0 +1,16 @@ +[ + { + "name": "Veeam Repository", + "sourceType": "Veeam Repository", + "icon": "hard-drive", + "singular": "Repository", + "plural": "Repositories" + }, + { + "name": "Veeam Job", + "sourceType": "Veeam Job", + "icon": "clock-rotate-left", + "singular": "Job", + "plural": "Jobs" + } +] diff --git a/plugins/VeeamBackupReplication/v1/dataStreams/jobStates.json b/plugins/VeeamBackupReplication/v1/dataStreams/jobStates.json new file mode 100644 index 00000000..3ce17201 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/dataStreams/jobStates.json @@ -0,0 +1,45 @@ +{ + "name": "jobStates", + "displayName": "Job Status (All)", + "description": "Status and last result of every Veeam Backup & Replication job", + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/v1/jobs/states", + "pathToData": "data", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "500" }, + "offset": { + "mode": "row", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "skip" } + }, + "getArgs": [], + "headers": [] + }, + "matches": "none", + "metadata": [ + { "sourceId": "id", "sourceType": "Veeam Job", "name": "name", "shape": "string", "role": "label" }, + { "name": "id", "displayName": "ID", "shape": "string", "visible": false }, + { "name": "type", "displayName": "Type" }, + { "name": "status", "displayName": "Status" }, + { "name": "lastResult", "displayName": "Last Result", "shape": ["state", { "map": { "success": ["Success"], "warning": ["Warning"], "error": ["Failed"], "unknown": ["None"] } }] }, + { "name": "workload", "displayName": "Workload" }, + { "name": "description", "displayName": "Description" }, + { "name": "lastRun", "displayName": "Last Run" }, + { "name": "nextRun", "displayName": "Next Run" }, + { "name": "nextRunPolicy", "displayName": "Next Run Policy" }, + { "name": "repositoryId", "displayName": "Repository Id" }, + { "name": "repositoryName", "displayName": "Repository Name" }, + { "name": "objectsCount", "displayName": "Objects Count" }, + { "name": "sessionId", "displayName": "Session Id" }, + { "name": "highPriority", "displayName": "High Priority" }, + { "name": "progressPercent", "displayName": "Progress Percent", "shape": "percent" }, + { "name": "sessionProgress", "displayName": "Session Progress" }, + { "name": "isStorageSnapshot", "displayName": "Is Storage Snapshot" } + ], + "timeframes": false +} diff --git a/plugins/VeeamBackupReplication/v1/dataStreams/jobStatesScoped.json b/plugins/VeeamBackupReplication/v1/dataStreams/jobStatesScoped.json new file mode 100644 index 00000000..48f94230 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/dataStreams/jobStatesScoped.json @@ -0,0 +1,40 @@ +{ + "name": "jobStatesScoped", + "displayName": "Job Status", + "description": "Status and last result of the selected Veeam Backup jobs (pick one or more jobs on the Objects tab)", + "baseDataSourceName": "httpRequestScopedSingle", + "matches": { + "sourceType": { + "type": "oneOf", + "values": ["Veeam Job"] + } + }, + "config": { + "httpMethod": "get", + "endpointPath": "api/v1/jobs/states", + "pathToData": "data", + "getArgs": [{ "key": "idFilter", "value": "{{object.rawId}}" }], + "headers": [] + }, + "metadata": [ + { "sourceId": "id", "sourceType": "Veeam Job", "name": "name", "shape": "string", "role": "label" }, + { "name": "id", "displayName": "ID", "shape": "string", "visible": false }, + { "name": "type", "displayName": "Type" }, + { "name": "status", "displayName": "Status" }, + { "name": "lastResult", "displayName": "Last Result", "shape": ["state", { "map": { "success": ["Success"], "warning": ["Warning"], "error": ["Failed"], "unknown": ["None"] } }] }, + { "name": "workload", "displayName": "Workload" }, + { "name": "description", "displayName": "Description" }, + { "name": "lastRun", "displayName": "Last Run" }, + { "name": "nextRun", "displayName": "Next Run" }, + { "name": "nextRunPolicy", "displayName": "Next Run Policy" }, + { "name": "repositoryId", "displayName": "Repository Id" }, + { "name": "repositoryName", "displayName": "Repository Name" }, + { "name": "objectsCount", "displayName": "Objects Count" }, + { "name": "sessionId", "displayName": "Session Id" }, + { "name": "highPriority", "displayName": "High Priority" }, + { "name": "progressPercent", "displayName": "Progress Percent", "shape": "percent" }, + { "name": "sessionProgress", "displayName": "Session Progress" }, + { "name": "isStorageSnapshot", "displayName": "Is Storage Snapshot" } + ], + "timeframes": false +} diff --git a/plugins/VeeamBackupReplication/v1/dataStreams/repositoryStates.json b/plugins/VeeamBackupReplication/v1/dataStreams/repositoryStates.json new file mode 100644 index 00000000..7e5bc423 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/dataStreams/repositoryStates.json @@ -0,0 +1,39 @@ +{ + "name": "repositoryStates", + "displayName": "Repository Status (All)", + "description": "Capacity, free space and online status of every Veeam Backup & Replication repository", + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "api/v1/backupInfrastructure/repositories/states", + "pathToData": "data", + "paging": { + "mode": "offset", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "500" }, + "offset": { + "mode": "row", + "rowCountIn": { "realm": "payloadArraySize", "path": "data" }, + "base": 0 + }, + "out": { "realm": "queryArg", "path": "skip" } + }, + "getArgs": [], + "headers": [] + }, + "matches": "none", + "metadata": [ + { "sourceId": "id", "sourceType": "Veeam Repository", "name": "name", "shape": "string", "role": "label" }, + { "name": "id", "displayName": "ID", "shape": "string", "visible": false }, + { "name": "type", "displayName": "Type" }, + { "name": "description", "displayName": "Description" }, + { "name": "hostId", "displayName": "Host Id" }, + { "name": "hostName", "displayName": "Host Name" }, + { "name": "path", "displayName": "Path" }, + { "name": "capacityGB", "displayName": "Capacity GB", "shape": "gigabytes" }, + { "name": "freeGB", "displayName": "Free GB", "shape": "gigabytes" }, + { "name": "usedSpaceGB", "displayName": "Used Space GB", "shape": "gigabytes" }, + { "name": "isOnline", "displayName": "Is Online" }, + { "name": "isOutOfDate", "displayName": "Is Out Of Date" } + ], + "timeframes": false +} diff --git a/plugins/VeeamBackupReplication/v1/dataStreams/repositoryStatesScoped.json b/plugins/VeeamBackupReplication/v1/dataStreams/repositoryStatesScoped.json new file mode 100644 index 00000000..148fc0c4 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/dataStreams/repositoryStatesScoped.json @@ -0,0 +1,34 @@ +{ + "name": "repositoryStatesScoped", + "displayName": "Repository Status", + "description": "Capacity and status of the selected Veeam Backup repositories (pick one or more repositories on the Objects tab)", + "baseDataSourceName": "httpRequestScopedSingle", + "matches": { + "sourceType": { + "type": "oneOf", + "values": ["Veeam Repository"] + } + }, + "config": { + "httpMethod": "get", + "endpointPath": "api/v1/backupInfrastructure/repositories/states", + "pathToData": "data", + "getArgs": [{ "key": "idFilter", "value": "{{object.rawId}}" }], + "headers": [] + }, + "metadata": [ + { "sourceId": "id", "sourceType": "Veeam Repository", "name": "name", "shape": "string", "role": "label" }, + { "name": "id", "displayName": "ID", "shape": "string", "visible": false }, + { "name": "type", "displayName": "Type" }, + { "name": "description", "displayName": "Description" }, + { "name": "hostId", "displayName": "Host Id" }, + { "name": "hostName", "displayName": "Host Name" }, + { "name": "path", "displayName": "Path" }, + { "name": "capacityGB", "displayName": "Capacity GB", "shape": "gigabytes" }, + { "name": "freeGB", "displayName": "Free GB", "shape": "gigabytes" }, + { "name": "usedSpaceGB", "displayName": "Used Space GB", "shape": "gigabytes" }, + { "name": "isOnline", "displayName": "Is Online" }, + { "name": "isOutOfDate", "displayName": "Is Out Of Date" } + ], + "timeframes": false +} diff --git a/plugins/VeeamBackupReplication/v1/defaultContent/Job/manifest.json b/plugins/VeeamBackupReplication/v1/defaultContent/Job/manifest.json new file mode 100644 index 00000000..8e01cb53 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/defaultContent/Job/manifest.json @@ -0,0 +1,5 @@ +{ + "items": [ + { "name": "status", "type": "dashboard" } + ] +} diff --git a/plugins/VeeamBackupReplication/v1/defaultContent/Job/status.dash.json b/plugins/VeeamBackupReplication/v1/defaultContent/Job/status.dash.json new file mode 100644 index 00000000..c1001b22 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/defaultContent/Job/status.dash.json @@ -0,0 +1,199 @@ +{ + "name": "Status", + "schemaVersion": "1.4", + "timeframe": "none", + "variables": ["{{variables.[Job]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "contents": [ + { + "static": false, + "w": 2, + "moved": false, + "h": 2, + "x": 0, + "y": 0, + "i": "e7be0bba-7347-4f63-91b9-e674407f4782", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Job]}}"], + "dataStream": { + "name": "jobStatesScoped", + "id": "{{dataStreams.[jobStatesScoped]}}", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "variable": "{{variables.[Job]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Jobs]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Last Result", + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "stateColumn": "lastResult", + "linkColumn": "none", + "labelColumn": "lastResult" + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 2, + "x": 2, + "y": 0, + "i": "9080ab8a-ba4a-4e8e-b555-785541a1a532", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Job]}}"], + "dataStream": { + "name": "jobStatesScoped", + "id": "{{dataStreams.[jobStatesScoped]}}", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "variable": "{{variables.[Job]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Jobs]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Current Status", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "status", + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 1, + "x": 0, + "y": 2, + "i": "b2743b43-f556-476a-85f8-c11f841bf608", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Job]}}"], + "dataStream": { + "name": "jobStatesScoped", + "id": "{{dataStreams.[jobStatesScoped]}}", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "variable": "{{variables.[Job]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Jobs]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Last Run Date/Time", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "lastRun", + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 2, + "x": 2, + "y": 2, + "i": "405b620b-5181-4f31-a1b2-74330dd53e5f", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Job]}}"], + "dataStream": { + "name": "jobStatesScoped", + "id": "{{dataStreams.[jobStatesScoped]}}", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "variable": "{{variables.[Job]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Jobs]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Properties", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["name", "type", "workload", "id", "description", "repositoryName", "objectsCount", "highPriority", "progressPercent", "isStorageSnapshot"], + "hiddenColumns": ["status", "lastResult", "lastRun", "nextRun", "nextRunPolicy", "repositoryId", "sessionId", "sessionProgress"], + "transpose": true + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 1, + "x": 0, + "y": 3, + "i": "04f72cbd-44de-4113-b9ff-3466b69dfcb6", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Job]}}"], + "dataStream": { + "name": "jobStatesScoped", + "id": "{{dataStreams.[jobStatesScoped]}}", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "variable": "{{variables.[Job]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Jobs]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Next Run Date/Time", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "nextRun", + "comparisonColumn": "none" + } + } + } + } + } + ] + } +} diff --git a/plugins/VeeamBackupReplication/v1/defaultContent/Repository/manifest.json b/plugins/VeeamBackupReplication/v1/defaultContent/Repository/manifest.json new file mode 100644 index 00000000..8e01cb53 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/defaultContent/Repository/manifest.json @@ -0,0 +1,5 @@ +{ + "items": [ + { "name": "status", "type": "dashboard" } + ] +} diff --git a/plugins/VeeamBackupReplication/v1/defaultContent/Repository/status.dash.json b/plugins/VeeamBackupReplication/v1/defaultContent/Repository/status.dash.json new file mode 100644 index 00000000..aef283c8 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/defaultContent/Repository/status.dash.json @@ -0,0 +1,270 @@ +{ + "name": "Status", + "schemaVersion": "1.4", + "timeframe": "none", + "variables": ["{{variables.[Repository]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "contents": [ + { + "static": false, + "w": 1, + "moved": false, + "h": 2, + "x": 0, + "y": 0, + "i": "538708c1-77b3-465f-9096-e6d4047f7860", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Repository]}}"], + "dataStream": { + "name": "repositoryStatesScoped", + "pluginConfigId": "{{configId}}", + "metadata": [ + { + "shape": ["state", { "map": { "unmonitored": [], "unknown": [], "error": [], "warning": [], "success": ["true"] } }], + "name": "isOnline" + } + ], + "id": "{{dataStreams.[repositoryStatesScoped]}}" + }, + "scope": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Online Status", + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "stateColumn": "isOnline", + "linkColumn": "name", + "labelColumn": "isOnline" + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 2, + "x": 1, + "y": 0, + "i": "27dce38f-8472-430a-9692-ad19e47caae6", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Repository]}}"], + "dataStream": { + "dataSourceConfig": { + "version": "2.0", + "tables": [ + { + "config": { + "timeframe": "none", + "variables": ["{{variables.[Repository]}}"], + "activePluginConfigIds": ["{{configId}}"], + "scope": { + "scope": "{{scopes.[Repositories]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Repository]}}" + }, + "dataStream": { + "name": "repositoryStatesScoped", + "id": "{{dataStreams.[repositoryStatesScoped]}}" + } + }, + "tableName": "dataset1" + } + ], + "sql": "SELECT\n \"Name\",\n ROUND(\n CASE WHEN \"capacityGB\" > 0\n THEN (\"freeGB\" / \"capacityGB\") * 100\n ELSE NULL\n END,\n 1\n ) AS \"Free Space %\"\nFROM dataset1\nORDER BY \"Free Space %\" ASC;" + }, + "metadata": [ + { + "shape": ["percent", { "thousandsSeparator": true, "asZeroToOne": false }], + "name": "Free Space %" + }, + { "pattern": ".*" } + ], + "id": "datastream-sql" + }, + "scope": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "% Free Space", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "Free Space %", + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 4, + "x": 2, + "y": 0, + "i": "d47021ed-0b21-4b27-a76b-bef8e8e4f40d", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Repository]}}"], + "dataStream": { + "name": "repositoryStatesScoped", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[repositoryStatesScoped]}}" + }, + "scope": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Properties", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["name", "type", "id", "description", "hostId", "hostName", "path", "isOutOfDate"], + "hiddenColumns": ["capacityGB", "freeGB", "usedSpaceGB", "isOnline"], + "transpose": true + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 1, + "x": 0, + "y": 2, + "i": "85e22c7b-1840-4168-8dcc-17eac6109207", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Repository]}}"], + "dataStream": { + "name": "repositoryStatesScoped", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[repositoryStatesScoped]}}" + }, + "scope": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Capacity", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "capacityGB", + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 1, + "x": 1, + "y": 2, + "i": "35b8d540-2e31-49ce-aaa1-ec39fdb011c6", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Repository]}}"], + "dataStream": { + "name": "repositoryStatesScoped", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[repositoryStatesScoped]}}" + }, + "scope": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Free", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "freeGB", + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 1, + "x": 0, + "y": 3, + "i": "e6dfaa10-5da4-4a30-b351-c3454ffece77", + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Repository]}}"], + "dataStream": { + "name": "repositoryStatesScoped", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[repositoryStatesScoped]}}" + }, + "scope": { + "variable": "{{variables.[Repository]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Repositories]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Backup Data Size", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "usedSpaceGB", + "comparisonColumn": "none" + } + } + } + } + } + ] + } +} diff --git a/plugins/VeeamBackupReplication/v1/defaultContent/manifest.json b/plugins/VeeamBackupReplication/v1/defaultContent/manifest.json new file mode 100644 index 00000000..b1f5b099 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/defaultContent/manifest.json @@ -0,0 +1,7 @@ +{ + "items": [ + { "name": "overviewDashboard", "type": "dashboard" }, + { "name": "Job", "type": "folder" }, + { "name": "Repository", "type": "folder" } + ] +} diff --git a/plugins/VeeamBackupReplication/v1/defaultContent/overviewDashboard.dash.json b/plugins/VeeamBackupReplication/v1/defaultContent/overviewDashboard.dash.json new file mode 100644 index 00000000..af937193 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/defaultContent/overviewDashboard.dash.json @@ -0,0 +1,286 @@ +{ + "name": "Overview", + "schemaVersion": "1.4", + "timeframe": "none", + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "contents": [ + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 0, + "y": 0, + "i": "60febbfa-c661-490f-906c-14f639ed9853", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "jobStates", + "filter": { "multiOperation": "and", "filters": [] }, + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[jobStates]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Jobs - Last Result", + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "builtin_count_by_label", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "right", + "legendMode": "table", + "labelColumn": "lastResult" + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 1, + "y": 0, + "i": "1a8da8a0-7fc9-4318-bd41-fb1cc12fe768", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "jobStates", + "filter": { "multiOperation": "and", "filters": [] }, + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[jobStates]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Jobs - Status", + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "builtin_count_by_label", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "right", + "legendMode": "table", + "labelColumn": "status" + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 2, + "y": 0, + "i": "8ab5f085-ab18-400e-bcb5-f52b28896eb2", + "z": 0, + "config": { + "_type": "tile/data-stream", + "timeframe": "none", + "dataStream": { + "name": "jobStates", + "filter": { + "filters": [ + { "column": "lastResult", "operation": "equals", "value": "error" } + ], + "multiOperation": "and" + }, + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[jobStates]}}" + }, + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Jobs - Last Result = Failed", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "hiddenColumns": ["id", "type", "workload", "description", "nextRun", "nextRunPolicy", "repositoryId", "repositoryName", "objectsCount", "sessionId", "highPriority", "progressPercent", "sessionProgress", "isStorageSnapshot", "lastResult[Expanded].rawState"], + "columnOrder": [] + } + }, + "noDataMessage": "No jobs have a last run state of Failed" + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 3, + "y": 0, + "i": "bd0c8ff7-3fa7-48a3-a540-acad08eb77b2", + "z": 0, + "config": { + "_type": "tile/data-stream", + "timeframe": "none", + "dataStream": { + "name": "jobStates", + "filter": { + "filters": [ + { "column": "lastResult", "operation": "equals", "value": "warning" } + ], + "multiOperation": "and" + }, + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[jobStates]}}" + }, + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Jobs - Last Result = Warning", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "hiddenColumns": ["id", "type", "workload", "description", "nextRun", "nextRunPolicy", "repositoryId", "repositoryName", "objectsCount", "sessionId", "highPriority", "progressPercent", "sessionProgress", "isStorageSnapshot", "lastResult[Expanded].rawState"], + "columnOrder": [] + } + }, + "noDataMessage": "No jobs have a last run state of Warning" + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 3, + "x": 0, + "y": 3, + "i": "998e1918-f8ab-4aa0-ac3a-9b9b8c410f2d", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "repositoryStates", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[repositoryStates]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Repositories", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["name", "capacityGB", "freeGB", "type", "isOnline"], + "hiddenColumns": ["id", "description", "hostId", "hostName", "path", "isOutOfDate", "usedSpaceGB"] + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 2, + "y": 3, + "i": "dc2b5f9f-6c75-4622-b398-7f12beac9190", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "dataSourceConfig": { + "version": "2.0", + "tables": [ + { + "config": { + "timeframe": "none", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "name": "repositoryStates", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[repositoryStates]}}" + } + }, + "tableName": "dataset1" + } + ], + "sql": "SELECT\n \"Name\",\n ROUND(\n CASE WHEN \"capacityGB\" > 0\n THEN (\"freeGB\" / \"capacityGB\") * 100\n ELSE NULL\n END,\n 1\n ) AS \"Free Space %\"\nFROM dataset1\nORDER BY \"Free Space %\" ASC;" + }, + "metadata": [ + { + "shape": ["percent", { "thousandsSeparator": true, "asZeroToOne": false }], + "name": "Free Space %" + }, + { "pattern": ".*" } + ], + "id": "datastream-sql" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "% Free Space", + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "blockHeight": 70, + "linkColumn": "none", + "columns": 1, + "sublabel": "Free Space %", + "labelColumn": "name" + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 3, + "y": 3, + "i": "063d468d-94b1-4ad7-8692-b8a7845142e6", + "z": 0, + "config": { + "_type": "tile/data-stream", + "timeframe": "none", + "dataStream": { + "name": "repositoryStates", + "pluginConfigId": "{{configId}}", + "filter": { + "filters": [ + { "column": "isOnline", "operation": "equals", "value": "false" } + ], + "multiOperation": "and" + }, + "id": "{{dataStreams.[repositoryStates]}}", + "group": { "by": [], "aggregate": [] } + }, + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Repositories Not Online", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "hiddenColumns": ["id", "type", "description", "hostId", "hostName", "capacityGB", "freeGB", "usedSpaceGB", "isOutOfDate"], + "columnOrder": ["name", "path", "isOnline"] + } + }, + "noDataMessage": "All repositories are online" + } + } + } + ] + } +} diff --git a/plugins/VeeamBackupReplication/v1/defaultContent/scopes.json b/plugins/VeeamBackupReplication/v1/defaultContent/scopes.json new file mode 100644 index 00000000..07df4f59 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/defaultContent/scopes.json @@ -0,0 +1,26 @@ +[ + { + "name": "Jobs", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Veeam Job"] } + }, + "variable": { + "name": "Job", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Repositories", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Veeam Repository"] } + }, + "variable": { + "name": "Repository", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/VeeamBackupReplication/v1/docs/README.md b/plugins/VeeamBackupReplication/v1/docs/README.md new file mode 100644 index 00000000..8c2c3e0e --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/docs/README.md @@ -0,0 +1,105 @@ +# Veeam Backup & Replication + +This plugin connects to a **Veeam Backup & Replication (VBR)** server through its REST API and brings your backup estate into SquaredUp — repositories and jobs as objects, their live status and capacity as data streams, and ready‑made dashboards for both. + +## What this plugin monitors + +- **Backup repositories** — indexed as objects, with capacity / free / used space, online status and location. +- **Backup jobs** — indexed as objects, with current status, last‑run result (Success / Warning / Failed), schedule and session details. +- **Object‑scoped status** — pick specific jobs or repositories on a tile's **Objects** tab and see just those. +- **Out‑of‑the‑box dashboards** — an Overview plus per‑object "Status" perspectives (see [Dashboards](#dashboards)). + +## Prerequisites + +1. **Veeam Backup & Replication 12.0 – 13** with the REST API service running. The REST API listens on HTTPS port **9419** by default — confirm it is reachable by opening `https://:9419/api/swagger/ui/index.html` in a browser on the same network. +2. **A user account** that can sign in to VBR with at least the **Veeam Backup Viewer** role (read‑only is sufficient). A local Windows account or a domain account both work. +3. **Network connectivity** from SquaredUp to the server on port 9419. VBR is normally on‑premises, so you will usually connect this data source through a **SquaredUp Agent** (relay) that can reach the server. The plugin is *hybrid* — it can run in the cloud or via an agent. + +## Getting / preparing credentials + +The plugin authenticates with the **username and password** of a VBR account (OAuth2 *password* grant — SquaredUp exchanges them for a short‑lived access token and refreshes it automatically). No client ID / secret is required. + +- Use a dedicated monitoring account where possible, with the **Veeam Backup Viewer** role. +- For **domain accounts**, enter the username in `DOMAIN\user` form (for example `CONTOSO\svc-squaredup`). + +## Configuration fields + +| Field | Required | Description | +|---|---|---| +| **Server URL** | Yes | The bare root of your VBR server — scheme, host and REST API port only, e.g. `https://vbr01.contoso.com:9419`. **Do not** add a path such as `/api/v1` or a trailing slash; the plugin appends those itself. | +| **Username** | Yes | A VBR account with at least the Veeam Backup Viewer role. Domain accounts use `DOMAIN\user`. | +| **Password** | Yes | The password for the account above. | +| **REST API version** | Yes | The `x-api-version` revision sent on every request. Pick the revision matching your server — see [Choosing the REST API version](#choosing-the-rest-api-version). Defaults to `1.3-rev1`. | +| **Ignore certificate errors** | No | Enable when the server presents a self‑signed certificate. | + +### Choosing the REST API version + +Every request carries an `x-api-version` header. **Choose the highest revision your Veeam server supports** — each maps to a specific build: + +| Revision | Veeam build | +|---|---| +| `1.3-rev1` (default) | 13.0.1 | +| `1.3-rev0` | 13.0.0 | +| `1.2-rev1` | 12.3.1 | +| `1.2-rev0` | 12.3.0 | +| `1.1-rev2` | 12.2 | +| `1.1-rev1` | 12.1 | +| `1.1-rev0` | 12.0 | + +> Choosing a revision **older** than your server can cause errors, because the server may return values (such as a `Stopped` job status) that the older schema cannot represent. When in doubt, use the highest revision your server supports. + +## What gets indexed + +| Object type | Represents | Key properties | +|---|---|---| +| **Veeam Repository** | A backup repository | repository type, description, host, path | +| **Veeam Job** | A backup job | job type, description | + +Objects are re‑imported automatically (every 12 hours by default), and are indexed from the job‑ and repository‑*state* endpoints so the same identity powers both the object list and its live status. Use them to scope dashboards, drill down, and search across SquaredUp. In any table, the **Name** column links straight to the matching object. + +## Data streams + +| Data stream | Scope | Returns | +|---|---|---| +| **Job Status (All)** | All jobs | Every job's status, last result, last/next run, repository, session details | +| **Repository Status (All)** | All repositories | Every repository's capacity, free/used space (GB), online status, host and path | +| **Job Status** | Selected job(s) | Same as *Job Status (All)*, filtered to the jobs you pick on the **Objects** tab | +| **Repository Status** | Selected repository(ies) | Same as *Repository Status (All)*, filtered to the repositories you pick | + +The two scoped streams (**Job Status** / **Repository Status**) filter **server‑side** — one request per selected object using the API's `idFilter` — and require at least one object to be selected. Notable column shapes: **Last Result** renders as a health dot (Success 🟢 / Warning 🟠 / Failed 🔴 / None ⚪), capacity/free/used render as **GB**, and job progress as a **percentage**. + +## Dashboards + +- **Overview** (global): jobs by last result and by status (donuts), lists of jobs whose last run **Failed** or ended in **Warning**, a repositories table, a **% Free Space** breakdown, and any **repositories that are not online**. +- **Job → Status** (per job): current status, last result, last / next run times, and full job properties. +- **Repository → Status** (per repository): online status, capacity, free and used space, **% free space**, and full repository properties. + +The per‑object dashboards appear as perspectives when you view a Veeam Job or Veeam Repository. + +## Compatibility + +- **Runtime‑tested against Veeam 13 (`1.3-rev1`).** Earlier versions (12.0 – 12.3) are supported through the matching REST API revision above; the endpoints the plugin uses exist in all of these revisions, but they have not been end‑to‑end tested by SquaredUp Labs. +- **Hybrid** — runs in the SquaredUp cloud or through an agent; on‑premises servers require an agent. + +## Known limitations + +- **On‑premises connectivity** — because VBR is usually on‑premises, a SquaredUp Agent is normally required to reach the server on port 9419. +- **Self‑signed certificates** — VBR defaults to a self‑signed certificate; enable *Ignore certificate errors* if you have not installed a trusted one. +- **REST API version must not be older than the server** — the `x-api-version` is sent on every request. If you upgrade VBR, raise the field to match (see above). +- **Object‑scoped streams need a selection** — *Job Status* / *Repository Status* make no request until at least one object is chosen; use the *(All)* streams for an unfiltered view. +- **Permissions** — the account must have at least the Veeam Backup Viewer role, or requests are rejected. + +## Troubleshooting + +| Symptom | Likely cause & fix | +|---|---| +| **HTTP 400 on the token request** | Credentials rejected — check the username/password and that the account can sign in to VBR (domain accounts need `DOMAIN\user`). | +| **HTTP 404 on every request** | The **Server URL** is wrong — it must be the bare root including the `:9419` port, with **no** `/api/v1` and no trailing slash. | +| **HTTP 500 mentioning an enum (e.g. `EJobStatus … Stopped`)** | The selected **REST API version** is older than your server. Raise it to the revision matching your build. | +| **A scoped tile shows no data** | *Job Status* / *Repository Status* need at least one object selected on the **Objects** tab. | +| **TLS / certificate error** | Enable **Ignore certificate errors** (VBR uses a self‑signed certificate by default). | + +## Additional resources + +- [Veeam Backup & Replication REST API reference (1.3-rev1)](https://helpcenter.veeam.com/references/vbr/13/rest/1.3-rev1/tag/SectionOverview) +- Swagger UI on your server: `https://:9419/api/swagger/ui/index.html` diff --git a/plugins/VeeamBackupReplication/v1/icon.svg b/plugins/VeeamBackupReplication/v1/icon.svg new file mode 100644 index 00000000..fc3a2770 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/icon.svg @@ -0,0 +1,7 @@ + + Veeam + + + + + diff --git a/plugins/VeeamBackupReplication/v1/indexDefinitions/default.json b/plugins/VeeamBackupReplication/v1/indexDefinitions/default.json new file mode 100644 index 00000000..78491745 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/indexDefinitions/default.json @@ -0,0 +1,34 @@ +{ + "steps": [ + { + "name": "repositories", + "dataStream": { "name": "repositoryStates" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Veeam Repository" }, + "properties": [ + { "repositoryType": "type" }, + "description", + "hostName", + "path" + ] + } + }, + { + "name": "jobs", + "dataStream": { "name": "jobStates" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { "value": "Veeam Job" }, + "properties": [ + { "jobType": "type" }, + "description" + ] + } + } + ] +} diff --git a/plugins/VeeamBackupReplication/v1/metadata.json b/plugins/VeeamBackupReplication/v1/metadata.json new file mode 100644 index 00000000..8c83f303 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/metadata.json @@ -0,0 +1,51 @@ +{ + "name": "veeam-backup-replication", + "displayName": "Veeam Backup & Replication", + "version": "1.0.0", + "author": { + "name": "SquaredUp Labs", + "type": "labs" + }, + "description": "Monitor backup jobs and repositories", + "category": "Infrastructure", + "type": "hybrid", + "schemaVersion": "2.0", + "keywords": ["veeam", "backup", "replication", "vbr", "repository", "job"], + "objectTypes": ["Veeam Repository", "Veeam Job"], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/VeeamBackupReplication/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/VeeamBackupReplication/v1", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI-ci-wpPLUG4677", + "majorVersion": "1", + "config": { + "oauth2TokenExtraArgs": [], + "oauth2ClientSecret": "notused", + "oauth2ClientSecretLocationDuringAuth": "body", + "authMode": "oauth2", + "oauth2GrantType": "password", + "baseUrl": "{{serverUrl}}", + "oauth2ClientId": "notused", + "oauth2TokenUrl": "{{serverUrl}}/api/oauth2/token", + "oauth2PasswordGrantUserName": "{{username}}", + "oauth2PasswordGrantPassword": "{{password}}", + "oauth2Scope": "", + "ignoreCertificateErrors": "{{ignoreCertificateErrors}}", + "oauth2TokenExtraHeaders": [ + { "key": "x-api-version", "value": "{{apiVersion}}" } + ], + "headers": [ + { "key": "x-api-version", "value": "{{apiVersion}}" } + ] + } + } +} diff --git a/plugins/VeeamBackupReplication/v1/ui.json b/plugins/VeeamBackupReplication/v1/ui.json new file mode 100644 index 00000000..01978c59 --- /dev/null +++ b/plugins/VeeamBackupReplication/v1/ui.json @@ -0,0 +1,62 @@ +[ + { + "type": "text", + "name": "serverUrl", + "label": "Server URL", + "help": "The bare server root only — scheme, host, and REST API port (default `9419`). For example `https://your-vbr-server:9419`. Do **not** include a path such as `/api/v1` or a trailing slash; the plugin appends those automatically.", + "placeholder": "https://your-vbr-server:9419", + "validation": { + "required": true + } + }, + { + "type": "text", + "name": "username", + "label": "Username", + "help": "A Veeam Backup & Replication account with at least the **Veeam Backup Viewer** role. For domain accounts use the `DOMAIN\\user` format.", + "placeholder": "Enter a username", + "validation": { + "required": true + } + }, + { + "type": "password", + "name": "password", + "label": "Password", + "help": "The password for the account above.", + "placeholder": "Enter a password", + "validation": { + "required": true + } + }, + { + "type": "autocomplete", + "name": "apiVersion", + "label": "REST API version", + "help": "Choose the REST API revision that matches your Veeam Backup & Replication server — use the highest revision your server supports (the Veeam build is shown beside each). A revision older than your server can cause errors.", + "defaultValue": "1.3-rev1", + "isMulti": false, + "isClearable": false, + "validation": { + "required": true + }, + "data": { + "source": "fixed", + "values": [ + { "value": "1.3-rev1", "label": "1.3-rev1 (Veeam 13.0.1)" }, + { "value": "1.3-rev0", "label": "1.3-rev0 (Veeam 13.0.0)" }, + { "value": "1.2-rev1", "label": "1.2-rev1 (Veeam 12.3.1)" }, + { "value": "1.2-rev0", "label": "1.2-rev0 (Veeam 12.3.0)" }, + { "value": "1.1-rev2", "label": "1.1-rev2 (Veeam 12.2)" }, + { "value": "1.1-rev1", "label": "1.1-rev1 (Veeam 12.1)" }, + { "value": "1.1-rev0", "label": "1.1-rev0 (Veeam 12.0)" } + ] + } + }, + { + "type": "checkbox", + "name": "ignoreCertificateErrors", + "label": "Ignore certificate errors", + "help": "Enable when connecting to a server that uses a self-signed certificate." + } +] From 6886123ffd462bf6338a647a43bdb3ad9306b330 Mon Sep 17 00:00:00 2001 From: jame2O Date: Fri, 3 Jul 2026 09:53:24 +0100 Subject: [PATCH 2/3] feat: switch back to main WebAPI base plugin --- plugins/VeeamBackupReplication/v1/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/VeeamBackupReplication/v1/metadata.json b/plugins/VeeamBackupReplication/v1/metadata.json index 8c83f303..b7b1a2f5 100644 --- a/plugins/VeeamBackupReplication/v1/metadata.json +++ b/plugins/VeeamBackupReplication/v1/metadata.json @@ -25,7 +25,7 @@ } ], "base": { - "plugin": "WebAPI-ci-wpPLUG4677", + "plugin": "WebAPI", "majorVersion": "1", "config": { "oauth2TokenExtraArgs": [], From c38636704af06d5505c2092ee532e8b0255428ed Mon Sep 17 00:00:00 2001 From: vinbab Date: Fri, 3 Jul 2026 13:43:58 +0100 Subject: [PATCH 3/3] Address PR review comments - metadata.json: add terminal punctuation to description (CodeRabbit) - Repository Status dashboard: map isOnline "false" to error so offline repositories surface as unhealthy (CodeRabbit) - docs/README.md: drop the trailing-slash note and the redundant indexing paragraph (review nits) Co-Authored-By: Claude Opus 4.8 --- .../v1/defaultContent/Repository/status.dash.json | 2 +- plugins/VeeamBackupReplication/v1/docs/README.md | 4 +--- plugins/VeeamBackupReplication/v1/metadata.json | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/VeeamBackupReplication/v1/defaultContent/Repository/status.dash.json b/plugins/VeeamBackupReplication/v1/defaultContent/Repository/status.dash.json index aef283c8..7596bc9d 100644 --- a/plugins/VeeamBackupReplication/v1/defaultContent/Repository/status.dash.json +++ b/plugins/VeeamBackupReplication/v1/defaultContent/Repository/status.dash.json @@ -24,7 +24,7 @@ "pluginConfigId": "{{configId}}", "metadata": [ { - "shape": ["state", { "map": { "unmonitored": [], "unknown": [], "error": [], "warning": [], "success": ["true"] } }], + "shape": ["state", { "map": { "unmonitored": [], "unknown": [], "error": ["false"], "warning": [], "success": ["true"] } }], "name": "isOnline" } ], diff --git a/plugins/VeeamBackupReplication/v1/docs/README.md b/plugins/VeeamBackupReplication/v1/docs/README.md index 8c2c3e0e..ca60db65 100644 --- a/plugins/VeeamBackupReplication/v1/docs/README.md +++ b/plugins/VeeamBackupReplication/v1/docs/README.md @@ -26,7 +26,7 @@ The plugin authenticates with the **username and password** of a VBR account (OA | Field | Required | Description | |---|---|---| -| **Server URL** | Yes | The bare root of your VBR server — scheme, host and REST API port only, e.g. `https://vbr01.contoso.com:9419`. **Do not** add a path such as `/api/v1` or a trailing slash; the plugin appends those itself. | +| **Server URL** | Yes | The bare root of your VBR server — scheme, host and REST API port only, e.g. `https://vbr01.contoso.com:9419`. **Do not** add a path such as `/api/v1`; the plugin appends it. | | **Username** | Yes | A VBR account with at least the Veeam Backup Viewer role. Domain accounts use `DOMAIN\user`. | | **Password** | Yes | The password for the account above. | | **REST API version** | Yes | The `x-api-version` revision sent on every request. Pick the revision matching your server — see [Choosing the REST API version](#choosing-the-rest-api-version). Defaults to `1.3-rev1`. | @@ -55,8 +55,6 @@ Every request carries an `x-api-version` header. **Choose the highest revision y | **Veeam Repository** | A backup repository | repository type, description, host, path | | **Veeam Job** | A backup job | job type, description | -Objects are re‑imported automatically (every 12 hours by default), and are indexed from the job‑ and repository‑*state* endpoints so the same identity powers both the object list and its live status. Use them to scope dashboards, drill down, and search across SquaredUp. In any table, the **Name** column links straight to the matching object. - ## Data streams | Data stream | Scope | Returns | diff --git a/plugins/VeeamBackupReplication/v1/metadata.json b/plugins/VeeamBackupReplication/v1/metadata.json index b7b1a2f5..bbbdab64 100644 --- a/plugins/VeeamBackupReplication/v1/metadata.json +++ b/plugins/VeeamBackupReplication/v1/metadata.json @@ -6,7 +6,7 @@ "name": "SquaredUp Labs", "type": "labs" }, - "description": "Monitor backup jobs and repositories", + "description": "Monitor backup jobs and repositories.", "category": "Infrastructure", "type": "hybrid", "schemaVersion": "2.0",