n8n-nodes-mat-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/README.md +86 -0
- package/dist/credentials/MatApi.credentials.js +30 -0
- package/dist/nodes/MAT_Framework/Read/MATFrameworkRead.node.js +357 -0
- package/dist/nodes/MAT_Framework/Read/mat-framework-icon.png +0 -0
- package/dist/nodes/MAT_Framework/Trigger/MATFrameworkTrigger.node.js +276 -0
- package/dist/nodes/MAT_Framework/Trigger/mat-framework-icon.png +0 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
Copyright © 2026 Iquall Networks.
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the “Software”) are proprietary to Iquall Networks. Permission is granted solely to install and use the Software for integration with a valid and properly licensed MAT platform instance. Any use beyond the scope expressly permitted herein is strictly prohibited.
|
|
6
|
+
|
|
7
|
+
The following actions are strictly prohibited:
|
|
8
|
+
Modify, adapt, translate, reverse engineer, decompile, or disassemble the Software.
|
|
9
|
+
|
|
10
|
+
Create derivative works based on the Software.
|
|
11
|
+
|
|
12
|
+
Redistribute, sublicense, rent, lease, sell, or otherwise transfer the Software.
|
|
13
|
+
|
|
14
|
+
Publish the Software or make it publicly available without explicit written authorization from Iquall Networks.
|
|
15
|
+
|
|
16
|
+
Remove, alter, or obscure any proprietary notices.
|
|
17
|
+
The Software is licensed, not sold. All intellectual property rights in and to the Software remain the exclusive property of Iquall Networks. Any unauthorized use automatically terminates this license.
|
|
18
|
+
|
|
19
|
+
The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to merchantability, fitness for a particular purpose, and non-infringement. In no event shall Iquall Networks be liable for any claim, damages, or other liability arising from or in connection with the Software or its use.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
|
|
2
|
+
# MAT Framework n8n Community Node
|
|
3
|
+
This repository provides the official community node that integrates MAT Framework with n8n. The node enables n8n workflows to securely trigger and monitor network-impacting automations executed inside MAT Framework.
|
|
4
|
+
|
|
5
|
+
All execution logic, governance, policy enforcement, and auditing are handled entirely within MAT.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
Full documentation, configuration guides, compatibility notes, and release information are available at:
|
|
9
|
+
|
|
10
|
+
https://iquall.net/mat-n8n/
|
|
11
|
+
|
|
12
|
+
For production deployments, always refer to the official documentation page above. This repository does not duplicate or maintain detailed usage documentation.
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
The node acts as a thin client over MAT’s northbound API layer. It does not execute automation logic itself. All execution, governance, policy enforcement, and auditing are handled by MAT.
|
|
17
|
+
|
|
18
|
+
High-level flow:
|
|
19
|
+
n8n Workflow
|
|
20
|
+
↓
|
|
21
|
+
MAT Community Node
|
|
22
|
+
↓
|
|
23
|
+
MAT Framework API
|
|
24
|
+
↓
|
|
25
|
+
Network / OSS / BSS Domains
|
|
26
|
+
|
|
27
|
+
**What this Node does:**
|
|
28
|
+
- Triggers MAT automations or Bricks
|
|
29
|
+
- Pass structured input parameters
|
|
30
|
+
- Retrieves execution results
|
|
31
|
+
- Integrates MAT job outputs into n8n workflows
|
|
32
|
+
|
|
33
|
+
The node acts strictly as a thin client over MAT’s REST API. All authorization and validation are enforced server-side by MAT.
|
|
34
|
+
|
|
35
|
+
## Prerequisites
|
|
36
|
+
- A running MAT Framework instance (SaaS or On-Prem)
|
|
37
|
+
- A valid API token
|
|
38
|
+
- Network connectivity between n8n and your MAT tenant
|
|
39
|
+
- A valid commercial MAT license
|
|
40
|
+
|
|
41
|
+
## Security & Governance
|
|
42
|
+
|
|
43
|
+
This node does not execute automation logic locally and does not bypass MAT controls. All RBAC, CI/CD validation, approval gates, maintenance windows, and audit logging are enforced server-side within MAT.
|
|
44
|
+
|
|
45
|
+
When triggering a workflow:
|
|
46
|
+
1. n8n sends a request to MAT API.
|
|
47
|
+
2. MAT validates authentication and permissions.
|
|
48
|
+
3. MAT registers a Job internally.
|
|
49
|
+
4. Execution is performed within the MAT Engine.
|
|
50
|
+
5. Job status and output can be polled asynchronously.
|
|
51
|
+
|
|
52
|
+
The node does not bypass:
|
|
53
|
+
- Role-based access control
|
|
54
|
+
- Approval gates
|
|
55
|
+
- CI/CD enforcement
|
|
56
|
+
- Closed-loop policies
|
|
57
|
+
- Audit logging
|
|
58
|
+
|
|
59
|
+
## Error Handling
|
|
60
|
+
Errors returned by the node fall into two categories:
|
|
61
|
+
- Transport errors (network, timeout, TLS)
|
|
62
|
+
- Execution errors (returned by MAT API)
|
|
63
|
+
|
|
64
|
+
MAT-side execution errors include:
|
|
65
|
+
- Authorization failure
|
|
66
|
+
- Invalid workflow ID
|
|
67
|
+
- Parameter validation error
|
|
68
|
+
- Policy restriction
|
|
69
|
+
|
|
70
|
+
All execution logs remain traceable inside MAT.
|
|
71
|
+
|
|
72
|
+
## Security Notes
|
|
73
|
+
- The node stores credentials in n8n’s encrypted credential store.
|
|
74
|
+
- API tokens should be scoped according to least privilege.
|
|
75
|
+
- All sensitive execution logic remains server-side in MAT.
|
|
76
|
+
- No network configuration logic resides in this repository.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
This node is implemented in TypeScript and follows n8n community node structure.
|
|
80
|
+
|
|
81
|
+
## Scope
|
|
82
|
+
This repository does not include the MAT Engine, automation workflows or SLA/support services, nor does it expose proprietary Bricks. Use of MAT APIs requires a valid commercial license.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
Proprietary License.
|
|
86
|
+
See [LICENSE](./LICENSE) for details.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MatApi = void 0;
|
|
4
|
+
class MatApi {
|
|
5
|
+
name = 'matApi';
|
|
6
|
+
displayName = 'MAT API';
|
|
7
|
+
documentationUrl = 'https://iquall.net/mat-n8n/';
|
|
8
|
+
properties = [
|
|
9
|
+
{
|
|
10
|
+
displayName: 'API Key',
|
|
11
|
+
name: 'apiKey',
|
|
12
|
+
type: 'string',
|
|
13
|
+
typeOptions: {
|
|
14
|
+
password: true,
|
|
15
|
+
},
|
|
16
|
+
default: '',
|
|
17
|
+
required: true,
|
|
18
|
+
description: 'The API key for authenticating with the MAT platform',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
authenticate = {
|
|
22
|
+
type: 'generic',
|
|
23
|
+
properties: {
|
|
24
|
+
headers: {
|
|
25
|
+
apikey: '={{$credentials.apiKey}}',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
exports.MatApi = MatApi;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MATFrameworkRead = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
class MATFrameworkRead {
|
|
6
|
+
description = {
|
|
7
|
+
displayName: 'MAT Framework Read',
|
|
8
|
+
name: 'matFrameworkRead',
|
|
9
|
+
icon: 'file:mat-framework-icon.png',
|
|
10
|
+
group: ['transform'],
|
|
11
|
+
version: 1,
|
|
12
|
+
subtitle: '={{$parameter["readOperation"]}}',
|
|
13
|
+
description: 'Read job data and reports from MAT platform',
|
|
14
|
+
documentationUrl: 'https://iquall.net/mat-n8n/',
|
|
15
|
+
defaults: {
|
|
16
|
+
name: 'MAT Framework Read',
|
|
17
|
+
},
|
|
18
|
+
inputs: ['main'],
|
|
19
|
+
outputs: ['main'],
|
|
20
|
+
credentials: [
|
|
21
|
+
{
|
|
22
|
+
name: 'matApi',
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
properties: [
|
|
27
|
+
{
|
|
28
|
+
displayName: 'MAT REST API URL',
|
|
29
|
+
name: 'baseUrl',
|
|
30
|
+
type: 'string',
|
|
31
|
+
default: '',
|
|
32
|
+
placeholder: 'https://your-domain.com/namespace/api/v2',
|
|
33
|
+
required: true,
|
|
34
|
+
description: 'The base URL for your MAT platform API (must end with /api/v2). Protocol (https://) will be added automatically if not provided.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
displayName: 'Environment',
|
|
38
|
+
name: 'environment',
|
|
39
|
+
type: 'options',
|
|
40
|
+
options: [
|
|
41
|
+
{
|
|
42
|
+
name: 'Production',
|
|
43
|
+
value: 'production',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Preproduction',
|
|
47
|
+
value: 'preproduction',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Sandbox',
|
|
51
|
+
value: 'sandbox',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
default: 'production',
|
|
55
|
+
required: true,
|
|
56
|
+
description: 'The environment to read data from',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
displayName: 'Automation Type',
|
|
60
|
+
name: 'automationType',
|
|
61
|
+
type: 'options',
|
|
62
|
+
options: [
|
|
63
|
+
{
|
|
64
|
+
name: 'Network Task',
|
|
65
|
+
value: 'networktask',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'Network Workflow',
|
|
69
|
+
value: 'networkworkflow',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Orchestration Workflow',
|
|
73
|
+
value: 'orchestrationworkflow',
|
|
74
|
+
},
|
|
75
|
+
// {
|
|
76
|
+
// name: 'Serverless Function',
|
|
77
|
+
// value: 'serverlessfunctions',
|
|
78
|
+
// },
|
|
79
|
+
],
|
|
80
|
+
default: 'networktask',
|
|
81
|
+
required: true,
|
|
82
|
+
description: 'The type of automation',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
displayName: 'Operation',
|
|
86
|
+
name: 'readOperation',
|
|
87
|
+
type: 'options',
|
|
88
|
+
options: [
|
|
89
|
+
{
|
|
90
|
+
name: 'Get Job Information',
|
|
91
|
+
value: 'jobDetails',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'View Job Logs',
|
|
95
|
+
value: 'logs',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'Get Network Elements',
|
|
99
|
+
value: 'networkelements',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'Get Job Result',
|
|
103
|
+
value: 'result',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'Get Job Report',
|
|
107
|
+
value: 'report',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'Download Report File',
|
|
111
|
+
value: 'reportFile',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'Get Alarms',
|
|
115
|
+
value: 'alarms',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
default: 'jobDetails',
|
|
119
|
+
required: true,
|
|
120
|
+
description: 'The type of data to retrieve',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
displayName: 'Automation ID',
|
|
124
|
+
name: 'automationId',
|
|
125
|
+
type: 'string',
|
|
126
|
+
default: '',
|
|
127
|
+
required: true,
|
|
128
|
+
displayOptions: {
|
|
129
|
+
hide: {
|
|
130
|
+
automationType: ['serverlessfunctions'],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
description: 'The ID of the automation',
|
|
134
|
+
},
|
|
135
|
+
// {
|
|
136
|
+
// displayName: 'SLF Namespace ID',
|
|
137
|
+
// name: 'slfNamespace',
|
|
138
|
+
// type: 'string',
|
|
139
|
+
// default: '',
|
|
140
|
+
// required: true,
|
|
141
|
+
// displayOptions: {
|
|
142
|
+
// show: {
|
|
143
|
+
// automationType: ['serverlessfunctions'],
|
|
144
|
+
// readOperation: ['alarms'],
|
|
145
|
+
// },
|
|
146
|
+
// },
|
|
147
|
+
// description: 'The namespace for the serverless function',
|
|
148
|
+
// },
|
|
149
|
+
// {
|
|
150
|
+
// displayName: 'Function Name',
|
|
151
|
+
// name: 'functionName',
|
|
152
|
+
// type: 'string',
|
|
153
|
+
// default: '',
|
|
154
|
+
// required: true,
|
|
155
|
+
// displayOptions: {
|
|
156
|
+
// show: {
|
|
157
|
+
// automationType: ['serverlessfunctions'],
|
|
158
|
+
// readOperation: ['alarms'],
|
|
159
|
+
// },
|
|
160
|
+
// },
|
|
161
|
+
// description: 'The name of the serverless function',
|
|
162
|
+
// },
|
|
163
|
+
{
|
|
164
|
+
displayName: 'Job ID',
|
|
165
|
+
name: 'jobId',
|
|
166
|
+
type: 'string',
|
|
167
|
+
default: '',
|
|
168
|
+
required: true,
|
|
169
|
+
displayOptions: {
|
|
170
|
+
show: {
|
|
171
|
+
readOperation: ['jobDetails', 'logs', 'networkelements', 'result', 'report', 'reportFile'],
|
|
172
|
+
},
|
|
173
|
+
hide: {
|
|
174
|
+
automationType: ['serverlessfunctions'],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
description: 'The ID of the job to retrieve data from',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
displayName: 'Report Name',
|
|
181
|
+
name: 'reportName',
|
|
182
|
+
type: 'string',
|
|
183
|
+
default: '',
|
|
184
|
+
required: true,
|
|
185
|
+
displayOptions: {
|
|
186
|
+
show: {
|
|
187
|
+
readOperation: ['report', 'reportFile'],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
description: 'The name of the report to retrieve',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
displayName: 'File ID',
|
|
194
|
+
name: 'fileId',
|
|
195
|
+
type: 'string',
|
|
196
|
+
default: '',
|
|
197
|
+
required: true,
|
|
198
|
+
displayOptions: {
|
|
199
|
+
show: {
|
|
200
|
+
readOperation: ['reportFile'],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
description: 'The ID of the file to download from the report',
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
async execute() {
|
|
208
|
+
const items = this.getInputData();
|
|
209
|
+
const returnData = [];
|
|
210
|
+
for (let i = 0; i < items.length; i++) {
|
|
211
|
+
try {
|
|
212
|
+
// 1. Get parameters
|
|
213
|
+
let baseUrl = this.getNodeParameter('baseUrl', i);
|
|
214
|
+
const environment = this.getNodeParameter('environment', i);
|
|
215
|
+
const automationType = this.getNodeParameter('automationType', i);
|
|
216
|
+
const readOperation = this.getNodeParameter('readOperation', i);
|
|
217
|
+
// 2. Get credentials
|
|
218
|
+
const credentials = await this.getCredentials('matApi', i);
|
|
219
|
+
const apiKey = credentials.apiKey;
|
|
220
|
+
// 3. Normalize and validate base URL
|
|
221
|
+
// Trim whitespace
|
|
222
|
+
baseUrl = baseUrl.trim();
|
|
223
|
+
// Add https:// if no protocol present
|
|
224
|
+
if (!baseUrl.startsWith('http://') && !baseUrl.startsWith('https://')) {
|
|
225
|
+
baseUrl = 'https://' + baseUrl;
|
|
226
|
+
}
|
|
227
|
+
// Remove trailing slashes
|
|
228
|
+
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
229
|
+
// Validate base URL ends with /api/v2 (case-insensitive)
|
|
230
|
+
if (!baseUrl.toLowerCase().endsWith('/api/v2')) {
|
|
231
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `MAT REST API URL must end with /api/v2\nProvided: ${baseUrl}\nExpected format: https://your-domain.com/namespace/api/v2`, { itemIndex: i });
|
|
232
|
+
}
|
|
233
|
+
// Ensure /api/v2 is lowercase for consistency
|
|
234
|
+
if (!baseUrl.endsWith('/api/v2')) {
|
|
235
|
+
baseUrl = baseUrl.substring(0, baseUrl.length - 7) + '/api/v2';
|
|
236
|
+
}
|
|
237
|
+
// 4. Validate operation compatibility
|
|
238
|
+
if (automationType === 'serverlessfunctions' && readOperation !== 'alarms') {
|
|
239
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Serverless Functions only support the "Get Alarms" operation', { itemIndex: i });
|
|
240
|
+
}
|
|
241
|
+
// 5. Build URL based on automation type and operation
|
|
242
|
+
let url;
|
|
243
|
+
if (readOperation === 'alarms') {
|
|
244
|
+
if (automationType === 'serverlessfunctions') {
|
|
245
|
+
const slfNamespace = this.getNodeParameter('slfNamespace', i);
|
|
246
|
+
const functionName = this.getNodeParameter('functionName', i);
|
|
247
|
+
url = `${baseUrl}/serverlessfunctions/namespace/${slfNamespace}/function/${functionName}/env/${environment}/alarms`;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
const automationId = this.getNodeParameter('automationId', i);
|
|
251
|
+
url = `${baseUrl}/${automationType}/${automationId}/env/${environment}/alarms`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// All other operations require automation_id and job_id
|
|
256
|
+
const automationId = this.getNodeParameter('automationId', i);
|
|
257
|
+
const jobId = this.getNodeParameter('jobId', i);
|
|
258
|
+
const baseJobUrl = `${baseUrl}/${automationType}/${automationId}/env/${environment}/jobs/${jobId}`;
|
|
259
|
+
switch (readOperation) {
|
|
260
|
+
case 'jobDetails':
|
|
261
|
+
url = baseJobUrl;
|
|
262
|
+
break;
|
|
263
|
+
case 'logs':
|
|
264
|
+
url = `${baseJobUrl}/logs`;
|
|
265
|
+
break;
|
|
266
|
+
case 'networkelements':
|
|
267
|
+
url = `${baseJobUrl}/networkelements`;
|
|
268
|
+
break;
|
|
269
|
+
case 'result':
|
|
270
|
+
url = `${baseJobUrl}/result`;
|
|
271
|
+
break;
|
|
272
|
+
case 'report':
|
|
273
|
+
const reportName = this.getNodeParameter('reportName', i);
|
|
274
|
+
url = `${baseJobUrl}/report/${reportName}`;
|
|
275
|
+
break;
|
|
276
|
+
case 'reportFile':
|
|
277
|
+
const reportNameForFile = this.getNodeParameter('reportName', i);
|
|
278
|
+
const fileId = this.getNodeParameter('fileId', i);
|
|
279
|
+
url = `${baseJobUrl}/report/${reportNameForFile}/file/${fileId}`;
|
|
280
|
+
break;
|
|
281
|
+
default:
|
|
282
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${readOperation}`, { itemIndex: i });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// 6. Make HTTP GET request
|
|
286
|
+
if (readOperation === 'reportFile') {
|
|
287
|
+
// Handle binary file download
|
|
288
|
+
const fileId = this.getNodeParameter('fileId', i);
|
|
289
|
+
const response = await this.helpers.request({
|
|
290
|
+
method: 'GET',
|
|
291
|
+
url: url,
|
|
292
|
+
headers: {
|
|
293
|
+
'apikey': apiKey,
|
|
294
|
+
},
|
|
295
|
+
encoding: null, // Important: don't encode binary data
|
|
296
|
+
resolveWithFullResponse: true,
|
|
297
|
+
});
|
|
298
|
+
// Extract filename from fileId or Content-Disposition header
|
|
299
|
+
let fileName = fileId;
|
|
300
|
+
const contentDisposition = response.headers['content-disposition'];
|
|
301
|
+
if (contentDisposition) {
|
|
302
|
+
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);
|
|
303
|
+
if (matches != null && matches[1]) {
|
|
304
|
+
fileName = matches[1].replace(/['"]/g, '');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Prepare binary data
|
|
308
|
+
const binaryData = await this.helpers.prepareBinaryData(response.body, fileName, response.headers['content-type'] || 'application/octet-stream');
|
|
309
|
+
// 7. Return as binary
|
|
310
|
+
returnData.push({
|
|
311
|
+
json: {
|
|
312
|
+
fileId: fileId,
|
|
313
|
+
fileName: fileName,
|
|
314
|
+
mimeType: response.headers['content-type'] || 'application/octet-stream',
|
|
315
|
+
},
|
|
316
|
+
binary: {
|
|
317
|
+
data: binaryData,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// Handle JSON responses (all other operations)
|
|
323
|
+
const options = {
|
|
324
|
+
method: 'GET',
|
|
325
|
+
url: url,
|
|
326
|
+
headers: {
|
|
327
|
+
'apikey': apiKey,
|
|
328
|
+
'Accept': 'application/json',
|
|
329
|
+
'Content-Type': 'application/json',
|
|
330
|
+
},
|
|
331
|
+
json: true,
|
|
332
|
+
};
|
|
333
|
+
const response = await this.helpers.request(options);
|
|
334
|
+
// 7. Return response
|
|
335
|
+
returnData.push({ json: response });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
if (this.continueOnFail()) {
|
|
340
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
341
|
+
const statusCode = error.statusCode || 'unknown';
|
|
342
|
+
returnData.push({
|
|
343
|
+
json: {
|
|
344
|
+
error: errorMessage,
|
|
345
|
+
statusCode: statusCode,
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex: i });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return [returnData];
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
exports.MATFrameworkRead = MATFrameworkRead;
|
|
Binary file
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MATFrameworkTrigger = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
class MATFrameworkTrigger {
|
|
6
|
+
description = {
|
|
7
|
+
displayName: 'MAT Framework Trigger',
|
|
8
|
+
name: 'matFrameworkTrigger',
|
|
9
|
+
icon: 'file:mat-framework-icon.png',
|
|
10
|
+
group: ['transform'],
|
|
11
|
+
version: 1,
|
|
12
|
+
subtitle: '={{$parameter["automationType"]}}',
|
|
13
|
+
description: 'Trigger MAT platform automations via simplified API',
|
|
14
|
+
documentationUrl: 'https://iquall.net/mat-n8n/',
|
|
15
|
+
defaults: {
|
|
16
|
+
name: 'MAT Framework Trigger',
|
|
17
|
+
},
|
|
18
|
+
inputs: ['main'],
|
|
19
|
+
outputs: ['main'],
|
|
20
|
+
credentials: [
|
|
21
|
+
{
|
|
22
|
+
name: 'matApi',
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
properties: [
|
|
27
|
+
{
|
|
28
|
+
displayName: 'MAT REST API URL',
|
|
29
|
+
name: 'baseUrl',
|
|
30
|
+
type: 'string',
|
|
31
|
+
default: '',
|
|
32
|
+
placeholder: 'https://your-domain.com/namespace/api/v2',
|
|
33
|
+
required: true,
|
|
34
|
+
description: 'The base URL for your MAT platform API (must end with /api/v2). Protocol (https://) will be added automatically if not provided.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
displayName: 'Environment',
|
|
38
|
+
name: 'environment',
|
|
39
|
+
type: 'options',
|
|
40
|
+
options: [
|
|
41
|
+
{
|
|
42
|
+
name: 'Production',
|
|
43
|
+
value: 'production',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Preproduction',
|
|
47
|
+
value: 'preproduction',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Sandbox',
|
|
51
|
+
value: 'sandbox',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
default: 'production',
|
|
55
|
+
required: true,
|
|
56
|
+
description: 'The environment to trigger the automation in',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
displayName: 'Automation Type',
|
|
60
|
+
name: 'automationType',
|
|
61
|
+
type: 'options',
|
|
62
|
+
options: [
|
|
63
|
+
{
|
|
64
|
+
name: 'Network Task',
|
|
65
|
+
value: 'networktask',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'Network Workflow',
|
|
69
|
+
value: 'networkworkflow',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Orchestration Workflow',
|
|
73
|
+
value: 'orchestrationworkflow',
|
|
74
|
+
},
|
|
75
|
+
// {
|
|
76
|
+
// name: 'Serverless Function',
|
|
77
|
+
// value: 'serverlessfunctions',
|
|
78
|
+
// },
|
|
79
|
+
],
|
|
80
|
+
default: 'networktask',
|
|
81
|
+
required: true,
|
|
82
|
+
description: 'The type of automation to trigger',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
displayName: 'Execution Mode',
|
|
86
|
+
name: 'syncMode',
|
|
87
|
+
type: 'options',
|
|
88
|
+
options: [
|
|
89
|
+
{
|
|
90
|
+
name: 'Async',
|
|
91
|
+
value: 'async',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'Sync',
|
|
95
|
+
value: 'sync',
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
default: 'async',
|
|
99
|
+
displayOptions: {
|
|
100
|
+
show: {
|
|
101
|
+
automationType: ['networktask'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
description: 'Whether to execute synchronously or asynchronously',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
displayName: 'Automation ID',
|
|
108
|
+
name: 'automationId',
|
|
109
|
+
type: 'string',
|
|
110
|
+
default: '',
|
|
111
|
+
required: true,
|
|
112
|
+
displayOptions: {
|
|
113
|
+
hide: {
|
|
114
|
+
automationType: ['serverlessfunctions'],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
description: 'The ID of the automation to trigger',
|
|
118
|
+
},
|
|
119
|
+
// {
|
|
120
|
+
// displayName: 'SLF Namespace ID',
|
|
121
|
+
// name: 'slfNamespace',
|
|
122
|
+
// type: 'string',
|
|
123
|
+
// default: '',
|
|
124
|
+
// required: true,
|
|
125
|
+
// displayOptions: {
|
|
126
|
+
// show: {
|
|
127
|
+
// automationType: ['serverlessfunctions'],
|
|
128
|
+
// },
|
|
129
|
+
// },
|
|
130
|
+
// description: 'The namespace for the serverless function',
|
|
131
|
+
// },
|
|
132
|
+
// {
|
|
133
|
+
// displayName: 'Function Name',
|
|
134
|
+
// name: 'functionName',
|
|
135
|
+
// type: 'string',
|
|
136
|
+
// default: '',
|
|
137
|
+
// required: true,
|
|
138
|
+
// displayOptions: {
|
|
139
|
+
// show: {
|
|
140
|
+
// automationType: ['serverlessfunctions'],
|
|
141
|
+
// },
|
|
142
|
+
// },
|
|
143
|
+
// description: 'The name of the serverless function to invoke',
|
|
144
|
+
// },
|
|
145
|
+
// {
|
|
146
|
+
// displayName: 'Request Body',
|
|
147
|
+
// name: 'requestBodyServerless',
|
|
148
|
+
// type: 'json',
|
|
149
|
+
// default: '{}',
|
|
150
|
+
// displayOptions: {
|
|
151
|
+
// show: {
|
|
152
|
+
// automationType: ['serverlessfunctions'],
|
|
153
|
+
// },
|
|
154
|
+
// },
|
|
155
|
+
// description: 'JSON body for Serverless Function requests.',
|
|
156
|
+
// },
|
|
157
|
+
{
|
|
158
|
+
displayName: 'Request Body',
|
|
159
|
+
name: 'requestBodyStandard',
|
|
160
|
+
type: 'json',
|
|
161
|
+
default: '{\n "form_data": {}\n}',
|
|
162
|
+
displayOptions: {
|
|
163
|
+
hide: {
|
|
164
|
+
automationType: ['serverlessfunctions'],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
description: 'JSON body for non-serverless requests. Keep payload under form_data.',
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
async execute() {
|
|
172
|
+
const items = this.getInputData();
|
|
173
|
+
const returnData = [];
|
|
174
|
+
for (let i = 0; i < items.length; i++) {
|
|
175
|
+
try {
|
|
176
|
+
// 1. Get all parameters
|
|
177
|
+
let baseUrl = this.getNodeParameter('baseUrl', i);
|
|
178
|
+
const environment = this.getNodeParameter('environment', i);
|
|
179
|
+
const automationType = this.getNodeParameter('automationType', i);
|
|
180
|
+
// 2. Get credentials
|
|
181
|
+
const credentials = await this.getCredentials('matApi', i);
|
|
182
|
+
const apiKey = credentials.apiKey;
|
|
183
|
+
// 3. Normalize and validate base URL
|
|
184
|
+
// Trim whitespace
|
|
185
|
+
baseUrl = baseUrl.trim();
|
|
186
|
+
// Add https:// if no protocol present
|
|
187
|
+
if (!baseUrl.startsWith('http://') && !baseUrl.startsWith('https://')) {
|
|
188
|
+
baseUrl = 'https://' + baseUrl;
|
|
189
|
+
}
|
|
190
|
+
// Remove trailing slashes
|
|
191
|
+
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
192
|
+
// Validate base URL ends with /api/v2 (case-insensitive)
|
|
193
|
+
if (!baseUrl.toLowerCase().endsWith('/api/v2')) {
|
|
194
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `MAT REST API URL must end with /api/v2\nProvided: ${baseUrl}\nExpected format: https://your-domain.com/namespace/api/v2`, { itemIndex: i });
|
|
195
|
+
}
|
|
196
|
+
// Ensure /api/v2 is lowercase for consistency
|
|
197
|
+
if (!baseUrl.endsWith('/api/v2')) {
|
|
198
|
+
baseUrl = baseUrl.substring(0, baseUrl.length - 7) + '/api/v2';
|
|
199
|
+
}
|
|
200
|
+
// 4. Build URL based on automation type
|
|
201
|
+
let url;
|
|
202
|
+
if (automationType === 'serverlessfunctions') {
|
|
203
|
+
const slfNamespace = this.getNodeParameter('slfNamespace', i);
|
|
204
|
+
const functionName = this.getNodeParameter('functionName', i);
|
|
205
|
+
url = `${baseUrl}/serverlessfunctions/namespace/${slfNamespace}/function/${functionName}/env/${environment}`;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const automationId = this.getNodeParameter('automationId', i);
|
|
209
|
+
url = `${baseUrl}/${automationType}/${automationId}/env/${environment}/jobs`;
|
|
210
|
+
// Add /sync suffix for Network Task + Sync mode
|
|
211
|
+
if (automationType === 'networktask') {
|
|
212
|
+
const syncMode = this.getNodeParameter('syncMode', i);
|
|
213
|
+
if (syncMode === 'sync') {
|
|
214
|
+
url += '/sync';
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// 5. Process request body
|
|
219
|
+
let requestBody;
|
|
220
|
+
const bodyParam = this.getNodeParameter(automationType === 'serverlessfunctions' ? 'requestBodyServerless' : 'requestBodyStandard', i);
|
|
221
|
+
// Parse JSON string if it's a string
|
|
222
|
+
if (typeof bodyParam === 'string') {
|
|
223
|
+
try {
|
|
224
|
+
requestBody = JSON.parse(bodyParam);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
228
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid JSON in Request Body: ${errorMessage}`, { itemIndex: i });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
requestBody = bodyParam;
|
|
233
|
+
}
|
|
234
|
+
// Only wrap in form_data for automation types that require it
|
|
235
|
+
// Serverless Functions expect plain JSON bodies
|
|
236
|
+
if (automationType !== 'serverlessfunctions') {
|
|
237
|
+
if (!Object.prototype.hasOwnProperty.call(requestBody ?? {}, 'form_data')) {
|
|
238
|
+
requestBody = { form_data: requestBody };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// 6. Make HTTP request
|
|
242
|
+
const options = {
|
|
243
|
+
method: 'POST',
|
|
244
|
+
url: url,
|
|
245
|
+
headers: {
|
|
246
|
+
'apikey': apiKey,
|
|
247
|
+
'Accept': 'application/json',
|
|
248
|
+
'Content-Type': 'application/json',
|
|
249
|
+
},
|
|
250
|
+
body: requestBody,
|
|
251
|
+
json: true,
|
|
252
|
+
};
|
|
253
|
+
const response = await this.helpers.request(options);
|
|
254
|
+
// 7. Return response
|
|
255
|
+
returnData.push({ json: response });
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (this.continueOnFail()) {
|
|
259
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
260
|
+
const statusCode = error.statusCode || 'unknown';
|
|
261
|
+
returnData.push({
|
|
262
|
+
json: {
|
|
263
|
+
error: errorMessage,
|
|
264
|
+
statusCode: statusCode,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex: i });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return [returnData];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
exports.MATFrameworkTrigger = MATFrameworkTrigger;
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-mat-framework",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official community nodes that integrate MAT Framework with n8n",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"mat-framework"
|
|
8
|
+
],
|
|
9
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
10
|
+
"homepage": "https://iquall.net/",
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Iquall Networks",
|
|
13
|
+
"email": "marketing@iquall.net"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && gulp build:icons",
|
|
17
|
+
"prepack": "npm run build",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"start": "npm run build && PKG_NAME=$(node -p \"require('./package.json').name\") && CUSTOM_DIR=\"$HOME/.n8n/custom\" && mkdir -p \"$CUSTOM_DIR\" && ln -sfn \"$(pwd)\" \"$CUSTOM_DIR/$PKG_NAME\" && N8N_CUSTOM_EXTENSIONS=\"$CUSTOM_DIR\" npx n8n",
|
|
20
|
+
"lint": "eslint nodes credentials --ext .ts",
|
|
21
|
+
"lintfix": "eslint nodes credentials --ext .ts --fix",
|
|
22
|
+
"format": "prettier nodes credentials --write"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"n8n": {
|
|
28
|
+
"n8nNodesApiVersion": 1,
|
|
29
|
+
"credentials": [
|
|
30
|
+
"dist/credentials/MatApi.credentials.js"
|
|
31
|
+
],
|
|
32
|
+
"nodes": [
|
|
33
|
+
"dist/nodes/MAT_Framework/Trigger/MATFrameworkTrigger.node.js",
|
|
34
|
+
"dist/nodes/MAT_Framework/Read/MATFrameworkRead.node.js"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
40
|
+
"eslint": "^8.56.0",
|
|
41
|
+
"eslint-plugin-n8n-nodes-base": "^1.16.1",
|
|
42
|
+
"gulp": "^4.0.2",
|
|
43
|
+
"n8n-workflow": "^1.0.0",
|
|
44
|
+
"prettier": "^3.0.0",
|
|
45
|
+
"typescript": "^5.3.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"n8n-workflow": "*"
|
|
49
|
+
}
|
|
50
|
+
}
|