diff --git a/nodes/CoveDataProtection/CoveDataProtection.node.ts b/nodes/CoveDataProtection/CoveDataProtection.node.ts
index 5f5a1cc..1c725f3 100644
--- a/nodes/CoveDataProtection/CoveDataProtection.node.ts
+++ b/nodes/CoveDataProtection/CoveDataProtection.node.ts
@@ -86,7 +86,13 @@ export class CoveDataProtection implements INodeType {
}
}
- return partners.sort((a, b) => a.name.localeCompare(b.name));
+ partners.sort((a, b) => a.name.localeCompare(b.name));
+ partners.unshift({
+ name: 'All Partners (Top Level)',
+ value: parentPartnerId,
+ });
+
+ return partners;
},
},
};
diff --git a/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/description.ts b/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/description.ts
new file mode 100644
index 0000000..e358c9c
--- /dev/null
+++ b/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/description.ts
@@ -0,0 +1,144 @@
+import { INodeProperties } from 'n8n-workflow';
+
+export const getBackupStatisticsDescription: INodeProperties[] = [
+ {
+ displayName: 'Partner Name or ID',
+ name: 'partnerId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getPartners',
+ },
+ displayOptions: {
+ show: {
+ resource: ['accounts'],
+ operation: ['getBackupStatistics'],
+ },
+ },
+ default: '',
+ description: 'The partner to enumerate device backup statistics for. Choose from the list, or specify an ID using an expression.',
+ required: true,
+ },
+ {
+ displayName: 'Data Sources',
+ name: 'dataSources',
+ type: 'multiOptions',
+ displayOptions: {
+ show: {
+ resource: ['accounts'],
+ operation: ['getBackupStatistics'],
+ },
+ },
+ options: [
+ { name: 'Bare Metal Restore (D17)', value: 'D17' },
+ { name: 'Files and Folders (D01)', value: 'D01' },
+ { name: 'Hyper-V (D14)', value: 'D14' },
+ { name: 'Microsoft 365 Exchange (D19)', value: 'D19' },
+ { name: 'Microsoft 365 OneDrive (D20)', value: 'D20' },
+ { name: 'Microsoft 365 SharePoint (D05)', value: 'D05' },
+ { name: 'Microsoft 365 Teams (D23)', value: 'D23' },
+ { name: 'MySql (D15)', value: 'D15' },
+ { name: 'Network Shares (D06)', value: 'D06' },
+ { name: 'Oracle (D12)', value: 'D12' },
+ { name: 'System State (D02)', value: 'D02' },
+ { name: 'Total (D09)', value: 'D09' },
+ { name: 'Virtual Disaster Recovery (D16)', value: 'D16' },
+ { name: 'VMware Virtual Machines (D08)', value: 'D08' },
+ { name: 'VssExchange (D04)', value: 'D04' },
+ { name: 'VssMsSql (D10)', value: 'D10' },
+ { name: 'VssSharePoint (D11)', value: 'D11' },
+ { name: 'VssSystemState (D07)', value: 'D07' },
+ ],
+ default: ['D01', 'D02'],
+ description: 'Which data sources to retrieve backup statistics for',
+ },
+ {
+ displayName: 'Fields',
+ name: 'fields',
+ type: 'multiOptions',
+ displayOptions: {
+ show: {
+ resource: ['accounts'],
+ operation: ['getBackupStatistics'],
+ },
+ },
+ options: [
+ { name: 'Last Session Status (F00)', value: 'F00' },
+ { name: 'Last Session Timestamp (F15)', value: 'F15' },
+ { name: 'Last Successful Session Status (F16)', value: 'F16' },
+ { name: 'Last Successful Session Timestamp (F09)', value: 'F09' },
+ { name: 'Last Completed Session Status (F17)', value: 'F17' },
+ { name: 'Last Completed Session Timestamp (F18)', value: 'F18' },
+ ],
+ default: ['F00', 'F15'],
+ description: 'Which backup statistics fields to retrieve for each selected data source',
+ },
+ {
+ displayName: 'Additional Options',
+ name: 'additionalOptions',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: ['accounts'],
+ operation: ['getBackupStatistics'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Filter',
+ name: 'filter',
+ type: 'string',
+ default: '',
+ description: 'Filter expression (e.g., "ANY =~ "Device*"")',
+ },
+ {
+ displayName: 'Selection Mode',
+ name: 'selectionMode',
+ type: 'options',
+ options: [
+ {
+ name: 'Merged',
+ value: 'Merged',
+ },
+ {
+ name: 'Detailed (Per Installation)',
+ value: 'PerInstallation',
+ },
+ ],
+ default: 'Merged',
+ description: 'Selection mode for statistics',
+ },
+ ],
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: ['accounts'],
+ operation: ['getBackupStatistics'],
+ },
+ },
+ default: true,
+ description: 'Whether to return all results or only up to a given limit',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource: ['accounts'],
+ operation: ['getBackupStatistics'],
+ returnAll: [false],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ },
+ default: 50,
+ description: 'Max number of results to return',
+ },
+];
diff --git a/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/execute.ts b/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/execute.ts
new file mode 100644
index 0000000..a8a43e1
--- /dev/null
+++ b/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/execute.ts
@@ -0,0 +1,119 @@
+import { IExecuteFunctions, IDataObject } from 'n8n-workflow';
+import { jsonRpcRequest } from '../../../transport';
+
+const SESSION_STATUS_LABELS: Record = {
+ '1': 'In process',
+ '2': 'Failed',
+ '3': 'Aborted',
+ '5': 'Completed',
+ '6': 'Interrupted',
+ '7': 'NotStarted',
+ '8': 'CompletedWithErrors',
+ '9': 'InProgressWithFaults',
+ '10': 'OverQuota',
+ '11': 'NoSelection',
+ '12': 'Restarted',
+};
+
+const FIELD_CODE_NAMES: Record = {
+ F00: 'lastSessionStatus',
+ F09: 'lastSuccessfulSessionTimestamp',
+ F15: 'lastSessionTimestamp',
+ F16: 'lastSuccessfulSessionStatus',
+ F17: 'lastCompletedSessionStatus',
+ F18: 'lastCompletedSessionTimestamp',
+};
+
+const STATUS_FIELD_CODES = new Set(['F00', 'F16', 'F17']);
+
+export async function execute(this: IExecuteFunctions, index: number): Promise {
+ const partnerId = this.getNodeParameter('partnerId', index) as number;
+ const dataSources = this.getNodeParameter('dataSources', index, []) as string[];
+ const fields = this.getNodeParameter('fields', index, []) as string[];
+ const additionalOptions = this.getNodeParameter('additionalOptions', index, {}) as IDataObject;
+ const returnAll = this.getNodeParameter('returnAll', index, true) as boolean;
+ const limit = returnAll ? 0 : (this.getNodeParameter('limit', index, 50) as number);
+
+ const dataSourceArray = dataSources.length > 0 ? dataSources : ['D01', 'D02'];
+ const fieldArray = fields.length > 0 ? fields : ['F00', 'F15'];
+
+ const backupColumns = dataSourceArray.flatMap((dataSource) =>
+ fieldArray.map((field) => `${dataSource}${field}`),
+ );
+
+ const columnArray = ['I0', 'I1', 'I18', 'I78', ...backupColumns];
+
+ const query: IDataObject = {
+ PartnerId: partnerId,
+ Columns: columnArray,
+ SelectionMode: additionalOptions.selectionMode || 'Merged',
+ StartRecordNumber: 0,
+ RecordsCount: returnAll ? 9999999 : limit,
+ };
+
+ if (additionalOptions.filter) {
+ query.Filter = additionalOptions.filter;
+ }
+
+ const params: IDataObject = {
+ query,
+ };
+
+ const result = await jsonRpcRequest.call(this, 'EnumerateAccountStatistics', params);
+
+ if (result && result.result && Array.isArray(result.result)) {
+ return result.result.map((item: any) => {
+ const normalized: IDataObject = {
+ accountId: item.AccountId,
+ partnerId: item.PartnerId,
+ flags: item.Flags,
+ backupStatistics: {},
+ };
+
+ const backupStatistics = normalized.backupStatistics as IDataObject;
+
+ if (item.Settings) {
+ item.Settings.forEach((setting: any) => {
+ Object.keys(setting).forEach((columnCode) => {
+ const value = setting[columnCode];
+
+ if (columnCode === 'I0') {
+ normalized.deviceId = value;
+ return;
+ }
+ if (columnCode === 'I1') {
+ normalized.deviceName = value;
+ return;
+ }
+ if (columnCode === 'I18') {
+ normalized.computerName = value;
+ return;
+ }
+ if (columnCode === 'I78') {
+ normalized.activeDataSources = (value as string).match(/.{1,3}/g) || [];
+ return;
+ }
+
+ const match = columnCode.match(/^(D\d{2})(F\d{2})$/);
+ if (match) {
+ const [, dataSourceCode, fieldCode] = match;
+ const fieldName = FIELD_CODE_NAMES[fieldCode] || fieldCode;
+
+ const dataSourceStats = (backupStatistics[dataSourceCode] ||
+ (backupStatistics[dataSourceCode] = {})) as IDataObject;
+
+ dataSourceStats[fieldName] = value;
+ if (STATUS_FIELD_CODES.has(fieldCode)) {
+ dataSourceStats[`${fieldName}Label`] = SESSION_STATUS_LABELS[value as string] || value;
+ }
+ }
+ });
+ });
+ }
+
+ return normalized;
+ });
+ }
+
+ return [];
+}
diff --git a/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/index.ts b/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/index.ts
new file mode 100644
index 0000000..0df790b
--- /dev/null
+++ b/nodes/CoveDataProtection/actions/accounts/getBackupStatistics/index.ts
@@ -0,0 +1,4 @@
+import { execute } from './execute';
+import { getBackupStatisticsDescription as description } from './description';
+
+export { description, execute };
diff --git a/nodes/CoveDataProtection/actions/accounts/getStatistics/description.ts b/nodes/CoveDataProtection/actions/accounts/getStatistics/description.ts
index badcc7d..f4cb3e1 100644
--- a/nodes/CoveDataProtection/actions/accounts/getStatistics/description.ts
+++ b/nodes/CoveDataProtection/actions/accounts/getStatistics/description.ts
@@ -80,8 +80,8 @@ export const getStatisticsDescription: INodeProperties[] = [
value: 'Merged',
},
{
- name: 'Detailed',
- value: 'Detailed',
+ name: 'Detailed (Per Installation)',
+ value: 'PerInstallation',
},
],
default: 'Merged',
diff --git a/nodes/CoveDataProtection/actions/accounts/index.ts b/nodes/CoveDataProtection/actions/accounts/index.ts
index b6f6476..901eac7 100644
--- a/nodes/CoveDataProtection/actions/accounts/index.ts
+++ b/nodes/CoveDataProtection/actions/accounts/index.ts
@@ -1,12 +1,13 @@
import * as getAll from './getAll';
import * as getById from './getById';
import * as getStatistics from './getStatistics';
+import * as getBackupStatistics from './getBackupStatistics';
import * as create from './create';
import * as update from './update';
import * as deleteAccount from './delete';
import { INodeProperties } from 'n8n-workflow';
-export { getAll, getById, getStatistics, create, update, deleteAccount as delete };
+export { getAll, getById, getStatistics, getBackupStatistics, create, update, deleteAccount as delete };
export const description: INodeProperties[] = [
{
@@ -32,6 +33,12 @@ export const description: INodeProperties[] = [
description: 'Delete an account',
action: 'Delete an account',
},
+ {
+ name: 'Get Backup Statistics',
+ value: 'getBackupStatistics',
+ description: 'Enumerate backup statistics (most recent backup date and status per data source) for devices',
+ action: 'Get account backup statistics',
+ },
{
name: 'Get by ID',
value: 'getById',
@@ -62,6 +69,7 @@ export const description: INodeProperties[] = [
...getAll.description,
...getById.description,
...getStatistics.description,
+ ...getBackupStatistics.description,
...create.description,
...update.description,
...deleteAccount.description,