diff --git a/plugins/ProxmoxVE/v1/configValidation.json b/plugins/ProxmoxVE/v1/configValidation.json index 9852aa46..420fcb92 100644 --- a/plugins/ProxmoxVE/v1/configValidation.json +++ b/plugins/ProxmoxVE/v1/configValidation.json @@ -6,6 +6,13 @@ "required": true, "error": "Could not connect to Proxmox VE. Check your Host URL is reachable, and that your Token ID and Token Secret are correct.", "success": "Connected to Proxmox VE successfully." + }, + { + "displayName": "Check Backup Server access", + "dataStream": { "name": "pbsVersion" }, + "required": false, + "error": "Could not connect to Proxmox Backup Server. If you enabled the Backup Server section, check the Host URL is reachable, and that your Token ID and Token Secret are correct. Backup Server metrics will be unavailable until this is fixed.", + "success": "Connected to Proxmox Backup Server successfully." } ] } diff --git a/plugins/ProxmoxVE/v1/custom_types.json b/plugins/ProxmoxVE/v1/custom_types.json index c4b2ff7a..407e902e 100644 --- a/plugins/ProxmoxVE/v1/custom_types.json +++ b/plugins/ProxmoxVE/v1/custom_types.json @@ -19,5 +19,19 @@ "icon": "box", "singular": "Container", "plural": "Containers" + }, + { + "name": "Proxmox Backup Datastore", + "sourceType": "Proxmox Backup Datastore", + "icon": "database", + "singular": "Backup Datastore", + "plural": "Backup Datastores" + }, + { + "name": "Proxmox Backup Group", + "sourceType": "Proxmox Backup Group", + "icon": "clock-rotate-left", + "singular": "Backup Group", + "plural": "Backup Groups" } ] diff --git a/plugins/ProxmoxVE/v1/dataStreams/pbsBackupGroups.json b/plugins/ProxmoxVE/v1/dataStreams/pbsBackupGroups.json new file mode 100644 index 00000000..bad590b5 --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/pbsBackupGroups.json @@ -0,0 +1,31 @@ +{ + "name": "pbsBackupGroups", + "displayName": "Backup Groups", + "description": "Backup groups (VMs, containers, and hosts backed up) within a datastore", + "tags": ["Backup Server", "Datastore", "Backup Group"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "baseUrl": "{{dataSource.pbsHost}}/api2/json", + "httpMethod": "get", + "endpointPath": "/admin/datastore/{{object.rawId}}/groups", + "headers": [ + { + "key": "Authorization", + "value": "PBSAPIToken {{dataSource.pbsTokenId}}:{{dataSource.pbsTokenSecret}}" + } + ], + "postRequestScript": "pbsBackupGroups.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Proxmox Backup Datastore"] } }, + "metadata": [ + { "name": "groupId", "displayName": "Group ID", "shape": "string", "visible": false }, + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "name": "store", "displayName": "Datastore", "shape": "string" }, + { "name": "backupType", "displayName": "Backup Type", "shape": "string" }, + { "name": "backupId", "displayName": "Backup ID", "shape": "string" }, + { "name": "owner", "displayName": "Owner", "shape": "string" }, + { "name": "lastBackup", "displayName": "Last Backup", "shape": "timestamp" }, + { "name": "backupCount", "displayName": "Backup Count", "shape": "number" } + ], + "timeframes": false +} diff --git a/plugins/ProxmoxVE/v1/dataStreams/pbsDatastoreUsage.json b/plugins/ProxmoxVE/v1/dataStreams/pbsDatastoreUsage.json new file mode 100644 index 00000000..93c064ad --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/pbsDatastoreUsage.json @@ -0,0 +1,26 @@ +{ + "name": "pbsDatastoreUsage", + "displayName": "Backup Server Datastore Usage", + "description": "Current storage capacity and usage for a Backup Server datastore", + "tags": ["Backup Server", "Datastore", "Status"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "baseUrl": "{{dataSource.pbsHost}}/api2/json", + "httpMethod": "get", + "endpointPath": "/admin/datastore/{{object.rawId}}/status", + "headers": [ + { + "key": "Authorization", + "value": "PBSAPIToken {{dataSource.pbsTokenId}}:{{dataSource.pbsTokenSecret}}" + } + ], + "pathToData": "data" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Proxmox Backup Datastore"] } }, + "metadata": [ + { "name": "used", "displayName": "Used", "shape": ["bytes", { "decimalPlaces": 2 }], "role": "value" }, + { "name": "total", "displayName": "Total", "shape": ["bytes", { "decimalPlaces": 2 }] }, + { "name": "avail", "displayName": "Available", "shape": ["bytes", { "decimalPlaces": 2 }] } + ], + "timeframes": false +} diff --git a/plugins/ProxmoxVE/v1/dataStreams/pbsDatastores.json b/plugins/ProxmoxVE/v1/dataStreams/pbsDatastores.json new file mode 100644 index 00000000..316d8db3 --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/pbsDatastores.json @@ -0,0 +1,26 @@ +{ + "name": "pbsDatastores", + "displayName": "Backup Server Datastores", + "description": "All datastores configured on the Proxmox Backup Server", + "tags": ["Backup Server", "Datastore"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "baseUrl": "{{dataSource.pbsHost}}/api2/json", + "httpMethod": "get", + "endpointPath": "/admin/datastore", + "headers": [ + { + "key": "Authorization", + "value": "PBSAPIToken {{dataSource.pbsTokenId}}:{{dataSource.pbsTokenSecret}}" + } + ], + "pathToData": "data" + }, + "matches": "none", + "visibility": { "type": "hidden" }, + "metadata": [ + { "name": "store", "displayName": "Datastore", "shape": "string", "role": "label" }, + { "name": "comment", "displayName": "Comment", "shape": "string" } + ], + "timeframes": false +} diff --git a/plugins/ProxmoxVE/v1/dataStreams/pbsGroupSnapshots.json b/plugins/ProxmoxVE/v1/dataStreams/pbsGroupSnapshots.json new file mode 100644 index 00000000..b039caf5 --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/pbsGroupSnapshots.json @@ -0,0 +1,33 @@ +{ + "name": "pbsGroupSnapshots", + "displayName": "Backup Group Snapshots", + "description": "Retained backup snapshots for a backup group, with size and verification status", + "tags": ["Backup Server", "Backup Group", "Snapshot"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "baseUrl": "{{dataSource.pbsHost}}/api2/json", + "httpMethod": "get", + "endpointPath": "/admin/datastore/{{object.store}}/snapshots", + "getArgs": [ + { "key": "backup-type", "value": "{{object.backupType}}" }, + { "key": "backup-id", "value": "{{object.backupId}}" } + ], + "headers": [ + { + "key": "Authorization", + "value": "PBSAPIToken {{dataSource.pbsTokenId}}:{{dataSource.pbsTokenSecret}}" + } + ], + "postRequestScript": "pbsGroupSnapshots.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Proxmox Backup Group"] } }, + "metadata": [ + { "name": "time", "displayName": "Time", "shape": "timestamp", "role": "timestamp" }, + { "name": "size", "displayName": "Size", "shape": "bytes", "role": "value" }, + { "name": "protected", "displayName": "Protected", "shape": "boolean" }, + { "name": "verificationState", "displayName": "Verification State", "shape": "string" }, + { "name": "comment", "displayName": "Comment", "shape": "string" }, + { "name": "backupGroup", "displayName": "Backup Group", "shape": "string" } + ], + "timeframes": false +} diff --git a/plugins/ProxmoxVE/v1/dataStreams/pbsMaintenanceTasks.json b/plugins/ProxmoxVE/v1/dataStreams/pbsMaintenanceTasks.json new file mode 100644 index 00000000..b0dfc2bd --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/pbsMaintenanceTasks.json @@ -0,0 +1,57 @@ +{ + "name": "pbsMaintenanceTasks", + "displayName": "Backup Server Tasks", + "description": "Task history for a datastore — backup, garbage collection, prune, and verify runs", + "tags": ["Backup Server", "Datastore", "Tasks"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "baseUrl": "{{dataSource.pbsHost}}/api2/json", + "httpMethod": "get", + "endpointPath": "/nodes/localhost/tasks", + "getArgs": [ + { "key": "store", "value": "{{object.rawId}}" }, + { "key": "since", "value": "{{timeframe.unixStart}}" }, + { "key": "until", "value": "{{timeframe.unixEnd}}" }, + { "key": "typefilter", "value": "{{taskType || null}}" }, + { "key": "limit", "value": "500" } + ], + "headers": [ + { + "key": "Authorization", + "value": "PBSAPIToken {{dataSource.pbsTokenId}}:{{dataSource.pbsTokenSecret}}" + } + ], + "postRequestScript": "pbsMaintenanceTasks.js" + }, + "matches": { "sourceType": { "type": "oneOf", "values": ["Proxmox Backup Datastore"] } }, + "ui": [ + { + "name": "taskType", + "type": "autocomplete", + "label": "Task type", + "allowCustomValues": false, + "isMulti": false, + "isClearable": true, + "data": { + "source": "fixed", + "values": [ + { "value": "backup", "label": "Backup" }, + { "value": "garbage_collection", "label": "Garbage Collection" }, + { "value": "prunejob", "label": "Prune" }, + { "value": "verificationjob", "label": "Verify" } + ] + } + } + ], + "metadata": [ + { "name": "upid", "displayName": "UPID", "shape": "string", "visible": false }, + { "name": "startTime", "displayName": "Started", "shape": "timestamp", "role": "timestamp" }, + { "name": "endTime", "displayName": "Ended", "shape": "timestamp" }, + { "name": "workerType", "displayName": "Task Type", "shape": "string" }, + { "name": "workerId", "displayName": "Target", "shape": "string" }, + { "name": "status", "displayName": "Status", "shape": "string" }, + { "name": "outcome", "displayName": "Outcome", "shape": ["state", { "map": { "success": ["success"], "error": ["error"], "warning": ["running"] } }] }, + { "name": "user", "displayName": "User", "shape": "string" } + ], + "timeframes": true +} diff --git a/plugins/ProxmoxVE/v1/dataStreams/pbsVersion.json b/plugins/ProxmoxVE/v1/dataStreams/pbsVersion.json new file mode 100644 index 00000000..e7aaf467 --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/pbsVersion.json @@ -0,0 +1,25 @@ +{ + "name": "pbsVersion", + "displayName": "Backup Server API Version", + "description": "Proxmox Backup Server API version and release information", + "tags": ["Backup Server", "Info"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "baseUrl": "{{dataSource.pbsHost}}/api2/json", + "httpMethod": "get", + "endpointPath": "/version", + "headers": [ + { + "key": "Authorization", + "value": "PBSAPIToken {{dataSource.pbsTokenId}}:{{dataSource.pbsTokenSecret}}" + } + ], + "pathToData": "data" + }, + "matches": "none", + "visibility": { "type": "hidden" }, + "metadata": [ + { "pattern": ".*" } + ], + "timeframes": false +} diff --git a/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsBackupGroups.js b/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsBackupGroups.js new file mode 100644 index 00000000..cb5e4ff1 --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsBackupGroups.js @@ -0,0 +1,15 @@ +const store = context.objects[0].rawId; +const groups = (data && data.data) || []; + +result = groups.map(function(g) { + return { + groupId: store + '/' + g['backup-type'] + '/' + g['backup-id'], + name: g['backup-type'] + '/' + g['backup-id'], + store: store, + backupType: g['backup-type'], + backupId: g['backup-id'], + owner: g.owner || null, + lastBackup: g['last-backup'] != null ? g['last-backup'] * 1000 : null, + backupCount: g['backup-count'] || 0 + }; +}); diff --git a/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsGroupSnapshots.js b/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsGroupSnapshots.js new file mode 100644 index 00000000..5d298b2e --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsGroupSnapshots.js @@ -0,0 +1,13 @@ +const snapshots = (data && data.data) || []; +const backupGroup = (context.objects[0] && context.objects[0].rawId) || null; + +result = snapshots.map(function(s) { + return { + time: s['backup-time'] != null ? s['backup-time'] * 1000 : null, + size: s.size || 0, + protected: !!s.protected, + verificationState: (s.verification && s.verification.state) || null, + comment: s.comment || null, + backupGroup: backupGroup + }; +}); diff --git a/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsMaintenanceTasks.js b/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsMaintenanceTasks.js new file mode 100644 index 00000000..e31ebcac --- /dev/null +++ b/plugins/ProxmoxVE/v1/dataStreams/scripts/pbsMaintenanceTasks.js @@ -0,0 +1,15 @@ +const tasks = (data && data.data) || []; + +result = tasks.map(function(t) { + const outcome = t.status == null ? 'running' : (t.status === 'OK' ? 'success' : 'error'); + return { + upid: t.upid, + startTime: t.starttime != null ? t.starttime * 1000 : null, + endTime: t.endtime != null ? t.endtime * 1000 : null, + workerType: t.worker_type, + workerId: t.worker_id || null, + status: t.status || null, + outcome: outcome, + user: t.user + }; +}); diff --git a/plugins/ProxmoxVE/v1/defaultContent/backupDatastoreDashboard.dash.json b/plugins/ProxmoxVE/v1/defaultContent/backupDatastoreDashboard.dash.json new file mode 100644 index 00000000..a40ffd2f --- /dev/null +++ b/plugins/ProxmoxVE/v1/defaultContent/backupDatastoreDashboard.dash.json @@ -0,0 +1,319 @@ +{ + "name": "Backup Datastore", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": ["{{variables.[Datastore]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "516c0044-8a60-417a-ba4e-60dfb20417ea", + "x": 0, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Datastore Details", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Datastore]}}" + }, + "variables": ["{{variables.[Datastore]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + }, + { + "i": "672f0f97-ba3a-44f9-8cac-b3fe5167a45f", + "x": 2, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Usage", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsDatastoreUsage]}}", + "name": "pbsDatastoreUsage", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Datastore]}}" + }, + "variables": ["{{variables.[Datastore]}}"], + "visualisation": { + "type": "data-stream-gauge", + "config": { + "data-stream-gauge": { + "value": { "type": "arr", "columns": ["used"] }, + "label": "Used", + "minimum": 0, + "maximum": 100, + "minimumColumn": "none", + "maximumColumn": "total" + } + } + } + } + }, + { + "i": "b15ebb85-6f20-4088-8ea2-2d493a298882", + "x": 0, + "y": 3, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Used", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsDatastoreUsage]}}", + "name": "pbsDatastoreUsage", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Datastore]}}" + }, + "variables": ["{{variables.[Datastore]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "used", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "4f2d0264-aa7c-4ce6-b421-7755df999cc3", + "x": 1, + "y": 3, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsDatastoreUsage]}}", + "name": "pbsDatastoreUsage", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Datastore]}}" + }, + "variables": ["{{variables.[Datastore]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "total", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "7566a4f7-9462-459f-851b-6d879d460d32", + "x": 2, + "y": 3, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Available", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsDatastoreUsage]}}", + "name": "pbsDatastoreUsage", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Datastore]}}" + }, + "variables": ["{{variables.[Datastore]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "avail", + "comparisonColumn": "none" + } + } + } + } + }, + { + "i": "ae9557c2-7694-40db-b7f8-bddfe601e985", + "x": 3, + "y": 3, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Backup Groups", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsBackupGroups]}}", + "name": "pbsBackupGroups", + "pluginConfigId": "{{configId}}", + "group": { + "by": [], + "aggregate": [{ "type": "count" }] + } + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Datastore]}}" + }, + "variables": ["{{variables.[Datastore]}}"], + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Backup Groups" + } + } + } + } + }, + { + "i": "360a1398-8cd4-48c5-aa29-ade776b3782f", + "x": 0, + "y": 5, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Backup Groups", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsBackupGroups]}}", + "name": "pbsBackupGroups", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["lastBackup", "desc"]] } + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Datastore]}}" + }, + "variables": ["{{variables.[Datastore]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["name", "backupType", "owner", "lastBackup", "backupCount"], + "hiddenColumns": ["groupId", "store"] + } + } + } + } + }, + { + "i": "d3a1e4c6-1f2b-4a3e-9c7d-8b6a5e4f3d2c", + "x": 0, + "y": 8, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Maintenance Tasks", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "{{dataStreams.[pbsMaintenanceTasks]}}", + "name": "pbsMaintenanceTasks", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["startTime", "desc"]] } + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Datastore]}}" + }, + "variables": ["{{variables.[Datastore]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["startTime", "workerType", "workerId", "status", "outcome", "endTime", "user"], + "hiddenColumns": ["upid"] + } + } + } + } + } + ] + } +} diff --git a/plugins/ProxmoxVE/v1/defaultContent/backupGroupDashboard.dash.json b/plugins/ProxmoxVE/v1/defaultContent/backupGroupDashboard.dash.json new file mode 100644 index 00000000..6a17c2fe --- /dev/null +++ b/plugins/ProxmoxVE/v1/defaultContent/backupGroupDashboard.dash.json @@ -0,0 +1,132 @@ +{ + "name": "Backup Group", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": ["{{variables.[Backup Group]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "2ef90cad-1ddb-4a5d-99a6-f6a90d41cd71", + "x": 0, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Backup Group Details", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Backup Groups]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Backup Group]}}" + }, + "variables": ["{{variables.[Backup Group]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + }, + { + "i": "baca838d-6a4f-428a-af84-6dabc55b25a0", + "x": 2, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Snapshots by Verification State", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsGroupSnapshots]}}", + "name": "pbsGroupSnapshots", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["verificationState", "uniqueValues"]], + "aggregate": [{ "type": "count" }] + } + }, + "scope": { + "scope": "{{scopes.[Backup Groups]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Backup Group]}}" + }, + "variables": ["{{variables.[Backup Group]}}"], + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "verificationState_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table" + } + } + } + } + }, + { + "i": "c6df7c70-5b98-400c-990d-f3a8b9c7e184", + "x": 0, + "y": 3, + "w": 4, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Snapshot History", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsGroupSnapshots]}}", + "name": "pbsGroupSnapshots", + "pluginConfigId": "{{configId}}", + "sort": { "by": [["time", "desc"]] } + }, + "scope": { + "scope": "{{scopes.[Backup Groups]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Backup Group]}}" + }, + "variables": ["{{variables.[Backup Group]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["time", "size", "protected", "verificationState", "comment"], + "hiddenColumns": ["backupGroup"] + } + } + } + } + } + ] + } +} diff --git a/plugins/ProxmoxVE/v1/defaultContent/manifest.json b/plugins/ProxmoxVE/v1/defaultContent/manifest.json index 39897465..72090684 100644 --- a/plugins/ProxmoxVE/v1/defaultContent/manifest.json +++ b/plugins/ProxmoxVE/v1/defaultContent/manifest.json @@ -3,6 +3,8 @@ { "name": "overviewDashboard", "type": "dashboard" }, { "name": "nodeDashboard", "type": "dashboard" }, { "name": "containerDashboard", "type": "dashboard" }, - { "name": "vmDashboard", "type": "dashboard" } + { "name": "vmDashboard", "type": "dashboard" }, + { "name": "backupDatastoreDashboard", "type": "dashboard" }, + { "name": "backupGroupDashboard", "type": "dashboard" } ] } diff --git a/plugins/ProxmoxVE/v1/defaultContent/overviewDashboard.dash.json b/plugins/ProxmoxVE/v1/defaultContent/overviewDashboard.dash.json index ffe2042e..4dd66adf 100644 --- a/plugins/ProxmoxVE/v1/defaultContent/overviewDashboard.dash.json +++ b/plugins/ProxmoxVE/v1/defaultContent/overviewDashboard.dash.json @@ -128,6 +128,343 @@ } } } + }, + { + "i": "3752dc7c-7b06-4970-b004-943cea5cc317", + "x": 0, + "y": 5, + "w": 4, + "h": 1, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/text", + "title": "", + "description": "", + "visualisation": { + "config": { + "content": "Backup Server", + "fontSize": 16, + "align": "left", + "autoSize": true + } + } + } + }, + { + "i": "ecebc19a-822d-4174-bc95-2ffb98d512f2", + "x": 0, + "y": 6, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Datastores", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "group": { "by": [], "aggregate": [{ "type": "count" }] } + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Datastores" + } + } + } + } + }, + { + "i": "36bf6edf-cdaa-4b36-a9fb-1704db7bc3f4", + "x": 1, + "y": 6, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Backup Groups", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "group": { "by": [], "aggregate": [{ "type": "count" }] } + }, + "scope": { + "scope": "{{scopes.[Backup Groups]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Backup Groups" + } + } + } + } + }, + { + "i": "1663f60b-d9e8-4181-9982-37a5a71ba118", + "x": 2, + "y": 6, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Backup Storage Used", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsDatastoreUsage]}}", + "name": "pbsDatastoreUsage", + "pluginConfigId": "{{configId}}", + "group": { "by": [], "aggregate": [{ "type": "sum", "names": ["used"] }] } + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "used_sum", + "comparisonColumn": "none", + "label": "Total Backup Storage Used" + } + } + } + } + }, + { + "i": "17fe133f-67e6-41e5-b75e-ebed6ec8c8c3", + "x": 3, + "y": 6, + "w": 1, + "h": 2, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Total Backup Storage Capacity", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsDatastoreUsage]}}", + "name": "pbsDatastoreUsage", + "pluginConfigId": "{{configId}}", + "group": { "by": [], "aggregate": [{ "type": "sum", "names": ["total"] }] } + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "total_sum", + "comparisonColumn": "none", + "label": "Total Backup Storage Capacity" + } + } + } + } + }, + { + "i": "92f57f43-80b0-405e-9b7e-0f9933603070", + "x": 0, + "y": 8, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Datastores", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Backup Datastores]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["name", "comment"], + "hiddenColumns": ["id", "links", "type", "label", "sourceId", "sourceType"] + } + } + } + } + }, + { + "i": "5b72527d-5a8a-4767-88a5-17b5b2b26091", + "x": 2, + "y": 8, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Backup Groups", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties", + "sort": { "by": [["lastBackup", "desc"]] } + }, + "scope": { + "scope": "{{scopes.[Backup Groups]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": false, + "columnOrder": ["name", "owner", "lastBackup", "backupCount"], + "hiddenColumns": ["id", "links", "type", "label", "sourceId", "sourceType", "store", "backupType", "backupId"] + } + } + } + } + }, + { + "i": "63d7bc42-162a-4bed-8158-3e2c98f8dd45", + "x": 0, + "y": 11, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Snapshots by Verification State", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsGroupSnapshots]}}", + "name": "pbsGroupSnapshots", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["verificationState", "uniqueValues"]], + "aggregate": [{ "type": "count" }] + } + }, + "scope": { + "scope": "{{scopes.[Backup Groups]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "verificationState_uniqueValues", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table" + } + } + } + } + }, + { + "i": "21668438-28e1-4c61-a9b0-388c5645869e", + "x": 2, + "y": 11, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Top Backup Groups by Snapshot Size", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[pbsGroupSnapshots]}}", + "name": "pbsGroupSnapshots", + "pluginConfigId": "{{configId}}", + "group": { + "by": [["backupGroup", "uniqueValues"]], + "aggregate": [{ "type": "sum", "names": ["size"] }] + }, + "sort": { "by": [["size_sum", "desc"]], "top": 10 } + }, + "scope": { + "scope": "{{scopes.[Backup Groups]}}", + "workspace": "{{workspaceId}}" + }, + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisData": "backupGroup_uniqueValues", + "yAxisData": ["size_sum"], + "xAxisGroup": "none", + "xAxisLabel": "", + "yAxisLabel": "Snapshot Size", + "showXAxisLabel": true, + "showYAxisLabel": true, + "showLegend": false, + "legendPosition": "bottom", + "showGrid": true, + "horizontalLayout": "vertical", + "displayMode": "actual", + "showTotals": false, + "showValue": false, + "grouping": false, + "range": { "type": "auto" } + } + } + } + } } ] } diff --git a/plugins/ProxmoxVE/v1/defaultContent/scopes.json b/plugins/ProxmoxVE/v1/defaultContent/scopes.json index 96f8e6c6..f68a927c 100644 --- a/plugins/ProxmoxVE/v1/defaultContent/scopes.json +++ b/plugins/ProxmoxVE/v1/defaultContent/scopes.json @@ -34,5 +34,29 @@ "default": "none", "type": "object" } + }, + { + "name": "Backup Datastores", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Proxmox Backup Datastore"] } + }, + "variable": { + "name": "Datastore", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Backup Groups", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Proxmox Backup Group"] } + }, + "variable": { + "name": "Backup Group", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } } ] diff --git a/plugins/ProxmoxVE/v1/docs/README.md b/plugins/ProxmoxVE/v1/docs/README.md index 03261e60..90627fff 100644 --- a/plugins/ProxmoxVE/v1/docs/README.md +++ b/plugins/ProxmoxVE/v1/docs/README.md @@ -1,7 +1,9 @@ -Monitor your Proxmox VE cluster from SquaredUp. This plugin imports nodes, virtual machines (QEMU/KVM), and LXC containers into the SquaredUp graph and provides dashboards for cluster health, resource utilisation, and per-object metrics. +Monitor your Proxmox VE cluster — and optionally the Proxmox Backup Server that backs it up — from SquaredUp. This plugin imports nodes, virtual machines (QEMU/KVM), LXC containers, backup datastores, and backup groups into the SquaredUp graph, and provides dashboards for cluster health, resource utilisation, and backup health. ## Prerequisites +### Proxmox VE + You need a Proxmox VE API token. Tokens are created per-user and can be scoped to read-only access. **Step 1 — Create an API token:** @@ -22,18 +24,45 @@ Assign the `PVEAuditor` role to the token at the datacenter level: **Note on self-signed certificates:** Proxmox uses a self-signed certificate by default. If your Proxmox host uses a self-signed or internal CA certificate, you may need to configure your SquaredUp agent to trust it, replace the certificate with one from a trusted CA, or enable the **Ignore certificate errors** option below. +### Proxmox Backup Server (optional) + +If you also want to monitor a Proxmox Backup Server (PBS) instance, enable the **Monitor a Proxmox Backup Server** toggle when configuring this plugin and create a second, separate API token on the PBS side. + +**Step 1 — Create an API token:** + +1. Log in to your Proxmox Backup Server web UI. +2. Go to **Configuration → Access Control → API Token**. +3. Click **Add**. +4. Select or create the user (e.g. `monitoring@pbs`), enter a Token ID (e.g. `squaredup`), and uncheck **Privilege Separation** if you want the token to inherit the user's permissions. +5. Click **Add** — the token secret is shown **once**. Copy it immediately. + +**Step 2 — Grant read permissions to both the user and the token:** + +Proxmox Backup Server calculates an API token's permissions from ACL entries granted to the **token itself**, capped by whatever its underlying **user** also has — a permission granted only to one or only to the other is not enough. Grant the `Audit` role at path `/` to **both**: + +1. Go to **Configuration → Access Control → Permissions → Add → API Token Permission**, set Path to `/`, User/Token to your token (e.g. `monitoring@pbs!squaredup`), Role to `Audit`, and check **Propagate**. +2. Repeat for the plain user (e.g. `monitoring@pbs`, without the `!tokenname` suffix) at the same path. + +Without the second grant, most of the plugin (datastores, backup groups, snapshots) still works, but the **Maintenance Tasks** tile will only show tasks the token itself performed — Proxmox Backup Server restricts a caller to seeing only its own tasks unless it (and its user) holds broader-than-read-only privilege on tasks run by other users, so `Audit` on both is the least-privilege combination that surfaces the full task history (e.g. scheduled Garbage Collection/Prune/Verify jobs typically run as `root@pam`, not your monitoring user). + +**Note on self-signed certificates:** the plugin's single **Ignore certificate errors** option (above) applies to both the Proxmox VE and Proxmox Backup Server connections. + ## What this plugin monitors **Imported objects:** - **Proxmox Node** — physical or virtual cluster member nodes - **Proxmox VM** — QEMU/KVM virtual machines - **Proxmox Container** — LXC containers +- **Proxmox Backup Datastore** — a backup storage pool on a Proxmox Backup Server *(optional — requires the Backup Server section to be configured)* +- **Proxmox Backup Group** — one backup target within a datastore (a VM, CT, or host being backed up) *(optional)* **Out-of-the-box dashboards:** -- **Overview** — cluster-wide resource summary, node health table, VM and container inventory +- **Overview** — cluster-wide resource summary, node health table, VM/container inventory, and — when Backup Server is configured — datastore usage, backup group status, and snapshot verification summary - **Node perspective** — per-node CPU, memory, and network metrics - **VM perspective** — per-VM CPU, memory, network, and disk I/O metrics - **Container perspective** — per-container CPU, memory, network, and disk I/O metrics +- **Backup Datastore perspective** — capacity/usage, backup groups in this datastore, and maintenance task history (backup/GC/prune/verify) +- **Backup Group perspective** — group identity/ownership and full snapshot history (size, protection, verification state) ## Configuration fields @@ -42,7 +71,11 @@ Assign the `PVEAuditor` role to the token at the datacenter level: | **Host URL** | Full URL to your Proxmox VE host, including port. Example: `https://192.168.1.100:8006` | Yes | | **Token ID** | The full API token identifier in the format `USER@REALM!TOKENID`. Example: `monitoring@pam!squaredup` | Yes | | **Token Secret** | The UUID secret displayed when the token was created. Example: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | Yes | -| **Ignore certificate errors** | Skip TLS certificate validation. Enable if your Proxmox host uses a self-signed certificate you haven't configured your agent to trust. | No | +| **Ignore certificate errors** | Skip TLS certificate validation for both Proxmox VE and (if configured) Proxmox Backup Server. Enable if either host uses a self-signed certificate you haven't configured your agent to trust. | No | +| **Monitor a Proxmox Backup Server** | Toggle to reveal and enable the Backup Server fields below. | No | +| **Backup Server Host URL** | Full URL to your Proxmox Backup Server instance, including port. Example: `https://192.168.1.101:8007` | Only if Backup Server is enabled | +| **Backup Server Token ID** | API token identifier in the format `USER@REALM!TOKENID`. Example: `monitoring@pbs!squaredup` | Only if Backup Server is enabled | +| **Backup Server Token Secret** | The secret shown when the Backup Server token was created. | Only if Backup Server is enabled | ## What gets indexed @@ -51,11 +84,15 @@ Assign the `PVEAuditor` role to the token at the datacenter level: | Proxmox Node | `/cluster/resources?type=node` | Node name (e.g. `pve1`) | | Proxmox VM | `/cluster/resources?type=vm` (filtered to `qemu`) | VM ID (e.g. `100`) | | Proxmox Container | `/cluster/resources?type=vm` (filtered to `lxc`) | Container ID (e.g. `101`) | +| Proxmox Backup Datastore | `/admin/datastore` | Datastore name (e.g. `teststore`) | +| Proxmox Backup Group | `/admin/datastore/{store}/groups` | Composite `{datastore}/{type}/{id}` (e.g. `teststore/ct/100`) — the API has no global group ID | ## Known limitations - **Write operations are not supported** — this plugin is read-only. -- **Storage metrics are not included** in v1. -- **RRD time-series data** uses Proxmox's fixed timeframe windows (`hour`, `day`, `week`, `month`) which are mapped to SquaredUp's timeframe picker. Data granularity is pre-aggregated by Proxmox (not raw per-second data). +- **RRD time-series data** (Proxmox VE metrics) uses Proxmox's fixed timeframe windows (`hour`, `day`, `week`, `month`) which are mapped to SquaredUp's timeframe picker. Data granularity is pre-aggregated by Proxmox (not raw per-second data). - **Self-signed TLS** — Proxmox ships with a self-signed certificate. If requests fail with TLS errors, replace the certificate or configure your agent to trust the Proxmox CA. - **Rate limits** — Proxmox VE does not enforce strict API rate limits, but excessive polling may impact cluster performance. +- **Proxmox Backup Server uses a different auth scheme than Proxmox VE** (`PBSAPIToken` vs `PVEAPIToken`, with a different token/secret separator) — handled internally by the plugin; the two token types are not interchangeable, so a PVE token cannot be reused against a PBS instance or vice versa. +- **Backup Server maintenance task visibility depends on permissions.** See the Prerequisites section above — without `Audit` granted to both the token and its underlying user at `/`, the Maintenance Tasks tile only shows tasks the monitoring token itself performed. +- **Backup Server datastore namespaces are not supported in v1** — backup groups are read from the default namespace only. diff --git a/plugins/ProxmoxVE/v1/indexDefinitions/default.json b/plugins/ProxmoxVE/v1/indexDefinitions/default.json index 04730806..77b296ce 100644 --- a/plugins/ProxmoxVE/v1/indexDefinitions/default.json +++ b/plugins/ProxmoxVE/v1/indexDefinitions/default.json @@ -13,6 +13,34 @@ "vmid" ] } + }, + { + "name": "pbsDatastores", + "dataStream": { "name": "pbsDatastores" }, + "timeframe": "none", + "objectMapping": { + "id": "store", + "name": "store", + "type": { "value": "Proxmox Backup Datastore" }, + "properties": ["comment"] + }, + "optional": true + }, + { + "name": "pbsBackupGroups", + "dataStream": { "name": "pbsBackupGroups" }, + "scope": { + "query": "g.V().has(\"sourceType\", \"Proxmox Backup Datastore\")" + }, + "dependsOn": ["pbsDatastores"], + "timeframe": "none", + "objectMapping": { + "id": "groupId", + "name": "name", + "type": { "value": "Proxmox Backup Group" }, + "properties": ["store", "backupType", "backupId", "owner", "lastBackup", "backupCount"] + }, + "optional": true } ] } diff --git a/plugins/ProxmoxVE/v1/metadata.json b/plugins/ProxmoxVE/v1/metadata.json index 7ba9b55d..e5303828 100644 --- a/plugins/ProxmoxVE/v1/metadata.json +++ b/plugins/ProxmoxVE/v1/metadata.json @@ -1,16 +1,16 @@ { "name": "proxmox-ve", "displayName": "Proxmox VE", - "version": "1.2.3", + "version": "1.3.0", "author": { "name": "@iBePixel", "type": "community" }, - "description": "Monitor your Proxmox VE cluster — import nodes, VMs, and LXC containers and track resource utilisation metrics.", + "description": "Monitor your Proxmox VE cluster and Proxmox Backup Server — import nodes, VMs, containers, backup datastores, and backup groups, and track resource utilisation and backup health.", "category": "Infrastructure", "type": "hybrid", "schemaVersion": "2.1", "importNotSupported": false, "restrictedToPlatforms": [], - "keywords": ["proxmox", "virtualization", "kvm", "lxc", "hypervisor", "infrastructure"], - "objectTypes": ["Proxmox Node", "Proxmox VM", "Proxmox Container"], + "keywords": ["proxmox", "virtualization", "kvm", "lxc", "hypervisor", "infrastructure", "proxmox backup server", "pbs", "backup"], + "objectTypes": ["Proxmox Node", "Proxmox VM", "Proxmox Container", "Proxmox Backup Datastore", "Proxmox Backup Group"], "links": [ { "category": "documentation", diff --git a/plugins/ProxmoxVE/v1/ui.json b/plugins/ProxmoxVE/v1/ui.json index 3b142490..3033c7f8 100644 --- a/plugins/ProxmoxVE/v1/ui.json +++ b/plugins/ProxmoxVE/v1/ui.json @@ -28,5 +28,41 @@ "name": "ignoreCertificateErrors", "label": "Ignore certificate errors", "help": "Enable when connecting to a Proxmox VE instance with a self-signed certificate." + }, + { + "type": "toggle", + "name": "monitorBackupServer", + "label": "Monitor a Proxmox Backup Server", + "help": "Enable to also monitor a Proxmox Backup Server instance that backs up this cluster — datastores, backup groups, snapshots, and maintenance tasks." + }, + { + "type": "fieldGroup", + "visible": { "monitorBackupServer": "true" }, + "fields": [ + { + "name": "pbsHost", + "label": "Backup Server Host URL", + "type": "url", + "help": "Full URL to your Proxmox Backup Server instance including port. Example: https://192.168.1.101:8007", + "placeholder": "https://192.168.1.101:8007", + "validation": { "required": true } + }, + { + "name": "pbsTokenId", + "label": "Backup Server Token ID", + "type": "text", + "help": "API token identifier in the format USER@REALM!TOKENID. Example: monitoring@pbs!squaredup", + "placeholder": "monitoring@pbs!squaredup", + "validation": { "required": true } + }, + { + "name": "pbsTokenSecret", + "label": "Backup Server Token Secret", + "type": "password", + "help": "The UUID secret displayed when the API token was created.", + "placeholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "validation": { "required": true } + } + ] } ]