@5minds/node-red-contrib-processcube 2.0.0-feature-629c78-m2dq1ygt → 7.6.0-develop-51b534-mjy3s4sm
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/README.md +3 -58
- package/check-authorization.html +138 -0
- package/check-authorization.js +27 -0
- package/dataobject-instance-query.html +141 -0
- package/dataobject-instance-query.js +45 -0
- package/endevent-finished-listener.js +14 -25
- package/examples/Check-Authorization-Sample.json +109 -0
- package/examples/Dataobject-Instance-Query-Sample.json +109 -0
- package/externaltask-error.html +8 -3
- package/externaltask-error.js +43 -29
- package/externaltask-event-listener.js +11 -28
- package/externaltask-input.html +48 -3
- package/externaltask-input.js +581 -114
- package/externaltask-output.js +20 -16
- package/icons/data-object-query.svg +5 -0
- package/message-event-trigger.html +1 -1
- package/message-event-trigger.js +8 -7
- package/package.json +74 -67
- package/process-event-listener.js +166 -225
- package/process-start.html +6 -0
- package/process-start.js +29 -6
- package/process-terminate.html +1 -1
- package/process-terminate.js +7 -5
- package/processcube-engine-config.html +25 -7
- package/processcube-engine-config.js +25 -135
- package/processcube-google-docs-mail-template.html +150 -0
- package/processcube-google-docs-mail-template.js +158 -0
- package/processdefinition-deploy.html +44 -0
- package/processdefinition-deploy.js +28 -0
- package/processdefinition-query.html +18 -13
- package/processdefinition-query.js +33 -31
- package/processinstance-delete-advanced.html +82 -0
- package/processinstance-delete-advanced.js +33 -0
- package/processinstance-delete.html +60 -8
- package/processinstance-delete.js +84 -30
- package/processinstance-query.html +116 -109
- package/processinstance-query.js +28 -5
- package/signal-event-trigger.js +8 -6
- package/usertask-event-listener.html +123 -1
- package/usertask-event-listener.js +30 -45
- package/usertask-input.html +119 -0
- package/usertask-input.js +7 -9
- package/usertask-output.js +15 -8
- package/wait-for-usertask.html +122 -6
- package/wait-for-usertask.js +44 -47
- package/.github/workflows/build-and-publish.yml +0 -72
- package/.processcube/authority/config/config.json +0 -36
- package/.processcube/authority/config/upeSeedingData.json +0 -12
- package/Dockerfile +0 -9
- package/doc_generator/_process_instances_query.md +0 -115
- package/doc_generator/generator.js +0 -41
- package/doc_generator/generator_with_swagger.js +0 -72
- package/doc_generator/package-lock.json +0 -176
- package/doc_generator/package.json +0 -15
- package/doc_generator/query_template.mustache +0 -20
- package/doc_generator/swagger.json +0 -4110
- package/docker-compose.yml +0 -44
- package/nodered/flows.json +0 -2156
- package/nodered/flows_cred.json +0 -3
- package/nodered/settings.js +0 -562
- package/nodered/static/ProcessCube_Logo.svg +0 -53
- package/processes/Call-Activity-Sample.bpmn +0 -88
- package/processes/External-Task-Auth-Sample.bpmn +0 -82
- package/processes/External-Task-Sample.bpmn +0 -94
- package/processes/SampleEvent.bpmn +0 -73
- package/processes/User-Task-Auth-Sample.bpmn +0 -63
- package/processes/User-Task-Sample.bpmn +0 -76
- package/processes/Wait-For-Usertask.bpmn +0 -74
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
defaults: {
|
|
5
5
|
name: { value: '' },
|
|
6
6
|
url: { value: 'http://engine:8000', required: true },
|
|
7
|
-
urlType: { type: 'str'},
|
|
7
|
+
urlType: { type: 'str' },
|
|
8
8
|
clientId: { value: '' },
|
|
9
9
|
clientIdType: { type: 'str' },
|
|
10
10
|
clientSecret: { value: '' },
|
|
11
11
|
clientSecretType: { type: 'str' },
|
|
12
|
+
scope: { value: 'engine_etw engine_read engine_write engine_observer' },
|
|
13
|
+
scopeType: { type: 'str' },
|
|
12
14
|
},
|
|
13
15
|
label: function () {
|
|
14
16
|
return this.name || this.url;
|
|
@@ -16,17 +18,22 @@
|
|
|
16
18
|
oneditprepare: function () {
|
|
17
19
|
$('#node-config-input-url').typedInput({
|
|
18
20
|
default: 'str',
|
|
19
|
-
types: ['str', '
|
|
21
|
+
types: ['str', 'env', 'cred'],
|
|
20
22
|
});
|
|
21
23
|
|
|
22
24
|
$('#node-config-input-clientId').typedInput({
|
|
23
25
|
default: 'str',
|
|
24
|
-
types: ['str', '
|
|
26
|
+
types: ['str', 'env', 'cred'],
|
|
25
27
|
});
|
|
26
28
|
|
|
27
29
|
$('#node-config-input-clientSecret').typedInput({
|
|
28
30
|
default: 'str',
|
|
29
|
-
types: ['str', '
|
|
31
|
+
types: ['str', 'env', 'cred'],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
$('#node-config-input-scope').typedInput({
|
|
35
|
+
default: 'str',
|
|
36
|
+
types: ['str', 'env', 'cred'],
|
|
30
37
|
});
|
|
31
38
|
|
|
32
39
|
$('#node-config-input-url').typedInput('value', this.url);
|
|
@@ -37,6 +44,9 @@
|
|
|
37
44
|
|
|
38
45
|
$('#node-config-input-clientSecret').typedInput('value', this.clientSecret);
|
|
39
46
|
$('#node-config-input-clientSecret').typedInput('type', this.clientSecretType);
|
|
47
|
+
|
|
48
|
+
$('#node-config-input-scope').typedInput('value', this.scope);
|
|
49
|
+
$('#node-config-input-scope').typedInput('type', this.scopeType);
|
|
40
50
|
},
|
|
41
51
|
oneditsave: function () {
|
|
42
52
|
this.url = $('#node-config-input-url').typedInput('value');
|
|
@@ -47,6 +57,9 @@
|
|
|
47
57
|
|
|
48
58
|
this.clientSecret = $('#node-config-input-clientSecret').typedInput('value');
|
|
49
59
|
this.clientSecretType = $('#node-config-input-clientSecret').typedInput('type');
|
|
60
|
+
|
|
61
|
+
this.scope = $('#node-config-input-scope').typedInput('value');
|
|
62
|
+
this.scopeType = $('#node-config-input-scope').typedInput('type');
|
|
50
63
|
},
|
|
51
64
|
});
|
|
52
65
|
</script>
|
|
@@ -66,7 +79,11 @@
|
|
|
66
79
|
</div>
|
|
67
80
|
<div class="form-row">
|
|
68
81
|
<label for="node-config-input-clientSecret"><i class="fa fa-bookmark"></i> Client secret</label>
|
|
69
|
-
<input type="
|
|
82
|
+
<input type="text" id="node-config-input-clientSecret" />
|
|
83
|
+
</div>
|
|
84
|
+
<div class="form-row">
|
|
85
|
+
<label for="node-config-input-scope"><i class="fa fa-bookmark"></i> Scope</label>
|
|
86
|
+
<input type="text" id="node-config-input-scope" />
|
|
70
87
|
</div>
|
|
71
88
|
</script>
|
|
72
89
|
|
|
@@ -78,9 +95,10 @@ The configuration for the ProcessCube engine.
|
|
|
78
95
|
: url (String) : The URL of the ProcessCube engine.
|
|
79
96
|
: clientId (String) : The client id for the ProcessCube engine.
|
|
80
97
|
: clientSecret (String) : The client secret for the ProcessCube engine.
|
|
98
|
+
: scope (String|Secret|ENV) : The scope for the ProcessCube engine.
|
|
81
99
|
|
|
82
100
|
### References
|
|
83
101
|
|
|
84
|
-
-
|
|
85
|
-
-
|
|
102
|
+
- [The ProcessCube© Developer Network](https://processcube.io) - All documentation for the ProcessCube© platform
|
|
103
|
+
- [ProcessCube© LowCode Integration](https://processcube.io/docs/node-red) - LowCode integration in ProcessCube©
|
|
86
104
|
</script>
|
|
@@ -1,72 +1,44 @@
|
|
|
1
1
|
const engine_client = require('@5minds/processcube_engine_client');
|
|
2
|
-
const jwt = require('jwt-decode');
|
|
3
|
-
const oidc = require('openid-client');
|
|
4
|
-
|
|
5
|
-
const DELAY_FACTOR = 0.85;
|
|
6
2
|
|
|
7
3
|
module.exports = function (RED) {
|
|
8
4
|
function ProcessCubeEngineNode(n) {
|
|
9
5
|
RED.nodes.createNode(this, n);
|
|
10
6
|
const node = this;
|
|
11
|
-
const identityChangedCallbacks = [];
|
|
12
|
-
this.url = RED.util.evaluateNodeProperty(n.url, n.urlType, node);
|
|
13
|
-
this.identity = null;
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
node.url = RED.util.evaluateNodeProperty(n.url, n.urlType, node);
|
|
9
|
+
node.credentials.clientId = RED.util.evaluateNodeProperty(n.clientId, n.clientIdType, node);
|
|
10
|
+
node.credentials.clientSecret = RED.util.evaluateNodeProperty(n.clientSecret, n.clientSecretType, node);
|
|
11
|
+
node.credentials.scope = RED.util.evaluateNodeProperty(n.scope, n.scopeType, node);
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
13
|
+
if (!node.credentials.scope) {
|
|
14
|
+
node.credentials.scope = 'engine_etw engine_read engine_write engine_observer';
|
|
15
|
+
}
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
17
|
+
try {
|
|
18
|
+
if (node.credentials.clientId && node.credentials.clientSecret) {
|
|
19
|
+
node.log('Create Client with secrets');
|
|
20
|
+
node.engineClient = new engine_client.EngineClient(node.url, {
|
|
21
|
+
clientId: node.credentials.clientId,
|
|
22
|
+
clientSecret: node.credentials.clientSecret,
|
|
23
|
+
scope: node.credentials.scope,
|
|
24
|
+
});
|
|
25
25
|
} else {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
this.setIdentity = (identity) => {
|
|
31
|
-
node.log(`setIdentity: ${JSON.stringify(identity)}`);
|
|
32
|
-
this.identity = identity;
|
|
33
|
-
|
|
34
|
-
for (const callback of identityChangedCallbacks) {
|
|
35
|
-
callback(identity);
|
|
26
|
+
node.log('Create Client without secrets');
|
|
27
|
+
node.engineClient = new engine_client.EngineClient(node.url);
|
|
36
28
|
}
|
|
37
|
-
}
|
|
29
|
+
} catch (error) {
|
|
30
|
+
node.error(error, {});
|
|
31
|
+
}
|
|
38
32
|
|
|
39
33
|
node.on('close', async () => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
node.log('close');
|
|
35
|
+
if (node.engineClient) {
|
|
36
|
+
node.engineClient.dispose();
|
|
37
|
+
node.engineClient = null;
|
|
43
38
|
}
|
|
44
39
|
});
|
|
45
|
-
|
|
46
|
-
if (this.credentials.clientId && this.credentials.clientSecret) {
|
|
47
|
-
this.engineClient = new engine_client.EngineClient(this.url);
|
|
48
|
-
|
|
49
|
-
this.engineClient.applicationInfo
|
|
50
|
-
.getAuthorityAddress()
|
|
51
|
-
.then((authorityUrl) => {
|
|
52
|
-
startRefreshingIdentityCycle(
|
|
53
|
-
this.credentials.clientId,
|
|
54
|
-
this.credentials.clientSecret,
|
|
55
|
-
authorityUrl,
|
|
56
|
-
node
|
|
57
|
-
).catch((reason) => {
|
|
58
|
-
console.error(reason);
|
|
59
|
-
node.error(reason);
|
|
60
|
-
});
|
|
61
|
-
})
|
|
62
|
-
.catch((reason) => {
|
|
63
|
-
console.error(reason);
|
|
64
|
-
node.error(reason);
|
|
65
|
-
});
|
|
66
|
-
} else {
|
|
67
|
-
this.engineClient = new engine_client.EngineClient(this.url);
|
|
68
|
-
}
|
|
69
40
|
}
|
|
41
|
+
|
|
70
42
|
RED.nodes.registerType('processcube-engine-config', ProcessCubeEngineNode, {
|
|
71
43
|
credentials: {
|
|
72
44
|
clientId: { type: 'text' },
|
|
@@ -74,85 +46,3 @@ module.exports = function (RED) {
|
|
|
74
46
|
},
|
|
75
47
|
});
|
|
76
48
|
};
|
|
77
|
-
|
|
78
|
-
async function getFreshTokenSet(clientId, clientSecret, authorityUrl) {
|
|
79
|
-
const issuer = await oidc.Issuer.discover(authorityUrl);
|
|
80
|
-
|
|
81
|
-
const client = new issuer.Client({
|
|
82
|
-
client_id: clientId,
|
|
83
|
-
client_secret: clientSecret,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const tokenSet = await client.grant({
|
|
87
|
-
grant_type: 'client_credentials',
|
|
88
|
-
scope: 'engine_etw engine_read engine_write',
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
return tokenSet;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function getIdentityForExternalTaskWorkers(tokenSet) {
|
|
95
|
-
const accessToken = tokenSet.access_token;
|
|
96
|
-
const decodedToken = jwt.jwtDecode(accessToken);
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
token: tokenSet.access_token,
|
|
100
|
-
userId: decodedToken.sub,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function getExpiresInForExternalTaskWorkers(tokenSet) {
|
|
105
|
-
let expiresIn = tokenSet.expires_in;
|
|
106
|
-
|
|
107
|
-
if (!expiresIn && tokenSet.expires_at) {
|
|
108
|
-
expiresIn = Math.floor(tokenSet.expires_at - Date.now() / 1000);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (expiresIn === undefined) {
|
|
112
|
-
throw new Error('Could not determine the time until the access token for external task workers expires');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return expiresIn;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Start refreshing the identity in regular intervals.
|
|
120
|
-
* @param {TokenSet | null} tokenSet The token set to refresh the identity for
|
|
121
|
-
* @returns {Promise<void>} A promise that resolves when the timer for refreshing the identity is initialized
|
|
122
|
-
* */
|
|
123
|
-
async function startRefreshingIdentityCycle(clientId, clientSecret, authorityUrl, configNode) {
|
|
124
|
-
let retries = 5;
|
|
125
|
-
|
|
126
|
-
const refresh = async () => {
|
|
127
|
-
try {
|
|
128
|
-
const newTokenSet = await getFreshTokenSet(clientId, clientSecret, authorityUrl);
|
|
129
|
-
const expiresIn = await getExpiresInForExternalTaskWorkers(newTokenSet);
|
|
130
|
-
const delay = expiresIn * DELAY_FACTOR * 1000;
|
|
131
|
-
|
|
132
|
-
freshIdentity = getIdentityForExternalTaskWorkers(newTokenSet);
|
|
133
|
-
|
|
134
|
-
configNode.setIdentity(freshIdentity);
|
|
135
|
-
|
|
136
|
-
retries = 5;
|
|
137
|
-
setTimeout(refresh, delay);
|
|
138
|
-
} catch (error) {
|
|
139
|
-
if (retries === 0) {
|
|
140
|
-
console.error(
|
|
141
|
-
'Could not refresh identity for external task worker processes. Stopping all external task workers.',
|
|
142
|
-
{ error }
|
|
143
|
-
);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
console.error('Could not refresh identity for external task worker processes.', {
|
|
147
|
-
error,
|
|
148
|
-
retryCount: retries,
|
|
149
|
-
});
|
|
150
|
-
retries--;
|
|
151
|
-
|
|
152
|
-
const delay = 2 * 1000;
|
|
153
|
-
setTimeout(refresh, delay);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
await refresh();
|
|
158
|
-
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('processcube-google-docs-mail-template', {
|
|
3
|
+
category: 'ProcessCube Tools',
|
|
4
|
+
color: '#02AFD6',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
template_link: { value: '', type: 'str' },
|
|
8
|
+
template_link_type: { value: 'str'},
|
|
9
|
+
},
|
|
10
|
+
inputs: 1,
|
|
11
|
+
outputs: 1,
|
|
12
|
+
icon: 'font-awesome/fa-sign-in',
|
|
13
|
+
label: function () {
|
|
14
|
+
return this.name || 'processcube-google-docs-mail-template';
|
|
15
|
+
},
|
|
16
|
+
oneditprepare: function () {
|
|
17
|
+
$('#node-input-template_link').typedInput({
|
|
18
|
+
default: 'msg',
|
|
19
|
+
types: ['msg', 'str'],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
$('#node-input-template_link').typedInput('value', this.template_link);
|
|
23
|
+
$('#node-input-template_link').typedInput('type', this.template_link_type);
|
|
24
|
+
},
|
|
25
|
+
oneditsave: function () {
|
|
26
|
+
(this.template_link = $('#node-input-template_link').typedInput('value')),
|
|
27
|
+
(this.template_link_type = $('#node-input-template_link').typedInput('type'));
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<script type="text/html" data-template-name="processcube-google-docs-mail-template">
|
|
33
|
+
<div class="form-row">
|
|
34
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
35
|
+
<input type="text" id="node-input-name" placeholder="Name" />
|
|
36
|
+
</div>
|
|
37
|
+
<div class="form-row">
|
|
38
|
+
<label for="node-input-template_link"><i class="fa fa-tag"></i> Template Link</label>
|
|
39
|
+
<input type="text" id="node-input-template_link" placeholder="Template Link" />
|
|
40
|
+
</div>
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<script type="text/markdown" data-help-name="processcube-google-docs-mail-template">
|
|
44
|
+
# Email Template Renderer (with Google Drive/Docs Support)
|
|
45
|
+
|
|
46
|
+
This Node-RED template module downloads a ZIP archive from a URL, extracts an HTML file and embedded images, replaces placeholders, and prepares the HTML content with inline attachments (CID) for email delivery.
|
|
47
|
+
|
|
48
|
+
## Google Drive / Docs Link Support
|
|
49
|
+
|
|
50
|
+
The ZIP file must be publicly accessible via a **shared Google Drive link** (at least “Anyone with the link can view”). The following link formats are supported:
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
// Format 0: Google Docs (export as ZIP)
|
|
54
|
+
const editMatch = link.match(/https:\/\/docs\.google\.com\/document\/d\/([^/]+)/);
|
|
55
|
+
if (editMatch) {
|
|
56
|
+
const fileId = editMatch[1];
|
|
57
|
+
return `https://docs.google.com/document/d/${fileId}/export?format=zip`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Format 1: Google Drive – shared file
|
|
61
|
+
const fileMatch = link.match(/https:\/\/drive\.google\.com\/file\/d\/([^/]+)\/view/);
|
|
62
|
+
if (fileMatch) {
|
|
63
|
+
return `https://drive.google.com/uc?export=download&id=${fileMatch[1]}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Format 2: Google Drive – open by ID
|
|
67
|
+
const openMatch = link.match(/https:\/\/drive\.google\.com\/open\?id=([^&]+)/);
|
|
68
|
+
if (openMatch) {
|
|
69
|
+
return `https://drive.google.com/uc?export=download&id=${openMatch[1]}`;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> Make sure that the file is shared with one of these formats and publicly accessible.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Processing Steps
|
|
78
|
+
|
|
79
|
+
1. **Download** the ZIP archive from the URL.
|
|
80
|
+
2. **Extract** the contents (expects one HTML file in the root directory).
|
|
81
|
+
3. **Embed images**: All images referenced in `images/` will be replaced with `cid:` links.
|
|
82
|
+
4. **Replace placeholders** in the following formats:
|
|
83
|
+
- `{{field}}`
|
|
84
|
+
- `///field///`
|
|
85
|
+
5. **Output**:
|
|
86
|
+
- rendered HTML string
|
|
87
|
+
- `msg.attachments[]` containing Nodemailer-compatible inline files
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Inputs
|
|
92
|
+
|
|
93
|
+
### `template_link` (String||msg): The URL to the ZIP archive containing the HTML template and images.
|
|
94
|
+
### `payload` (JSON): Field to replace placeholders in the HTML template.
|
|
95
|
+
|
|
96
|
+
Fields for placeholder replacement. Example:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"user_name": "Martin",
|
|
101
|
+
"newsletter_date": "May 5, 2025",
|
|
102
|
+
"announcement": "Our new album will be released soon!"
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Used to replace `{{user_name}}` or `///user_name///` in the HTML template.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Outputs
|
|
111
|
+
|
|
112
|
+
### `payload` (String)
|
|
113
|
+
|
|
114
|
+
The rendered HTML content with embedded CID references and replaced placeholders.
|
|
115
|
+
|
|
116
|
+
### `attachments` (Array)
|
|
117
|
+
|
|
118
|
+
Array of objects like:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"filename": "image1.png",
|
|
123
|
+
"path": "/path/to/file/image1.png",
|
|
124
|
+
"cid": "image1"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Example
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
<p>Hello {{user_name}},</p>
|
|
134
|
+
<img src="images/image1.png">
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Becomes:
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
<p>Hello Martin,</p>
|
|
141
|
+
<img src="cid:image1">
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## References
|
|
147
|
+
|
|
148
|
+
- [The ProcessCube Developer Network](https://processcube.io) – All documentation for the ProcessCube© platform
|
|
149
|
+
- [Node-RED Integration in ProcessCube©](https://processcube.io/docs/node-red) – Node-RED integration in ProcessCube©
|
|
150
|
+
</script>
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { pipeline } = require('stream');
|
|
6
|
+
const { promisify } = require('util');
|
|
7
|
+
const AdmZip = require('adm-zip');
|
|
8
|
+
const fetch = require('node-fetch');
|
|
9
|
+
|
|
10
|
+
const streamPipeline = promisify(pipeline);
|
|
11
|
+
|
|
12
|
+
function ProcesssCubeGoogleDocsMailTemplate(config) {
|
|
13
|
+
RED.nodes.createNode(this, config);
|
|
14
|
+
const node = this;
|
|
15
|
+
|
|
16
|
+
node.on('input', async function (msg) {
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
|
|
20
|
+
async function inlineCssImport(html) {
|
|
21
|
+
// 1. Finde @import-Zeile
|
|
22
|
+
const importRegex = /@import\s+url\(([^)]+)\);?/;
|
|
23
|
+
const match = html.match(importRegex);
|
|
24
|
+
if (!match) return html; // keine @import-Zeile gefunden
|
|
25
|
+
|
|
26
|
+
const url = match[1].replace(/['"]/g, ''); // evtl. Anführungszeichen entfernen
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(url);
|
|
30
|
+
if (!response.ok) throw new Error(`Fehler beim Laden von ${url}`);
|
|
31
|
+
|
|
32
|
+
const cssContent = await response.text();
|
|
33
|
+
|
|
34
|
+
// 2. Ersetze @import-Zeile durch eingebettetes CSS
|
|
35
|
+
const embeddedStyle = `\n/* inlined from ${url} */\n${cssContent}`;
|
|
36
|
+
return html.replace(importRegex, embeddedStyle);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Fehler beim Inlining der CSS-Datei:', error);
|
|
39
|
+
return html; // Fallback: Original belassen
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
function convertGoogleDriveLink(link) {
|
|
45
|
+
// Format 0: https://docs.google.com/document/d/FILE_ID/edit
|
|
46
|
+
const editMatch = link.match(/https:\/\/docs\.google\.com\/document\/d\/([^/]+)/);
|
|
47
|
+
if (editMatch) {
|
|
48
|
+
const fileId = editMatch[1];
|
|
49
|
+
return `https://docs.google.com/document/d/${fileId}/export?format=zip`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Format 1: https://drive.google.com/file/d/FILE_ID/view?usp=sharing
|
|
53
|
+
const fileMatch = link.match(/https:\/\/drive\.google\.com\/file\/d\/([^/]+)\/view/);
|
|
54
|
+
if (fileMatch) {
|
|
55
|
+
return `https://drive.google.com/uc?export=download&id=${fileMatch[1]}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Format 2: https://drive.google.com/open?id=FILE_ID
|
|
59
|
+
const openMatch = link.match(/https:\/\/drive\.google\.com\/open\?id=([^&]+)/);
|
|
60
|
+
if (openMatch) {
|
|
61
|
+
return `https://drive.google.com/uc?export=download&id=${openMatch[1]}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Anderenfalls unverändert zurückgeben
|
|
65
|
+
return link;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderTemplate(html, payload) {
|
|
69
|
+
// Ersetze {{feld}} und ///feld///
|
|
70
|
+
return html.replace(/({{([^}]+)}}|\/\/\/([^/]+)\/\/\/)/g, (match, _, field1, field2) => {
|
|
71
|
+
const key = field1 || field2;
|
|
72
|
+
return payload[key.trim()] ?? match; // fallback: Platzhalter bleibt bestehen
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function removeGoogleRedirects(html) {
|
|
77
|
+
return html.replace(/https:\/\/www\.google\.com\/url\?q=([^"&]+)[^"]*/g, (match, actualUrl) => {
|
|
78
|
+
try {
|
|
79
|
+
// Google-URLs sind URL-encoded – dekodieren
|
|
80
|
+
return decodeURIComponent(actualUrl);
|
|
81
|
+
} catch {
|
|
82
|
+
return actualUrl;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const template_link = RED.util.evaluateNodeProperty(config.template_link, config.template_link_type, node, msg);
|
|
88
|
+
|
|
89
|
+
const customRoot = path.resolve(RED.settings.userDir, 'tmp/processcube-google-docs-mail-template');
|
|
90
|
+
fs.mkdirSync(customRoot, { recursive: true });
|
|
91
|
+
const tempDir = fs.mkdtempSync(path.join(customRoot, 'run-'));
|
|
92
|
+
const zipPath = path.join(tempDir, 'downloaded.zip');
|
|
93
|
+
|
|
94
|
+
const url = convertGoogleDriveLink(template_link);
|
|
95
|
+
|
|
96
|
+
const response = await fetch(url);
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new Error(`Fehler beim Herunterladen: ${response.status} ${response.statusText}`);
|
|
99
|
+
}
|
|
100
|
+
await streamPipeline(response.body, fs.createWriteStream(zipPath));
|
|
101
|
+
|
|
102
|
+
const zip = new AdmZip(zipPath);
|
|
103
|
+
zip.extractAllTo(tempDir, true);
|
|
104
|
+
|
|
105
|
+
// === HTML-Datei im obersten Verzeichnis finden ===
|
|
106
|
+
const topLevelFiles = fs.readdirSync(tempDir, { withFileTypes: true });
|
|
107
|
+
const htmlEntry = topLevelFiles.find(file =>
|
|
108
|
+
file.isFile() && file.name.toLowerCase().endsWith('.html')
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (!htmlEntry) {
|
|
112
|
+
throw new Error('Keine HTML-Datei im ZIP-Hauptverzeichnis gefunden.');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const htmlPath = path.join(tempDir, htmlEntry.name);
|
|
116
|
+
let html = fs.readFileSync(htmlPath, 'utf8');
|
|
117
|
+
|
|
118
|
+
// === Bilder durch CID ersetzen ===
|
|
119
|
+
const imgRegex = /src="images\/([^"]+)"/g;
|
|
120
|
+
const attachments = [];
|
|
121
|
+
let match;
|
|
122
|
+
|
|
123
|
+
while ((match = imgRegex.exec(html)) !== null) {
|
|
124
|
+
const fileName = match[1];
|
|
125
|
+
const cidName = path.parse(fileName).name;
|
|
126
|
+
const imgPath = path.join(tempDir, 'images', fileName);
|
|
127
|
+
|
|
128
|
+
if (fs.existsSync(imgPath)) {
|
|
129
|
+
attachments.push({
|
|
130
|
+
filename: fileName,
|
|
131
|
+
path: imgPath,
|
|
132
|
+
cid: cidName
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
html = html.replace(`src="images/${fileName}"`, `src="cid:${cidName}"`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
html = await inlineCssImport(html);
|
|
140
|
+
|
|
141
|
+
let new_payload = renderTemplate(html, msg.payload);
|
|
142
|
+
|
|
143
|
+
// ggf. mit schalter
|
|
144
|
+
new_payload = removeGoogleRedirects(new_payload);
|
|
145
|
+
|
|
146
|
+
msg.payload = new_payload;
|
|
147
|
+
msg.attachments = attachments;
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
node.send(msg);
|
|
151
|
+
} catch (queryError) {
|
|
152
|
+
node.error(`Generate the content: ${queryError.message}`, msg);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
RED.nodes.registerType('processcube-google-docs-mail-template', ProcesssCubeGoogleDocsMailTemplate);
|
|
158
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('processdefinition-deploy', {
|
|
3
|
+
category: 'ProcessCube',
|
|
4
|
+
color: '#02AFD6',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
engine: { value: '', type: 'processcube-engine-config' },
|
|
8
|
+
},
|
|
9
|
+
inputs: 1,
|
|
10
|
+
outputs: 1,
|
|
11
|
+
icon: 'processdefinition_deploy.svg',
|
|
12
|
+
label: function () {
|
|
13
|
+
return this.name || 'processdefinition-deploy';
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script type="text/html" data-template-name="processdefinition-deploy">
|
|
19
|
+
<div class="form-row">
|
|
20
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
21
|
+
<input type="text" id="node-input-name" placeholder="Name" />
|
|
22
|
+
</div>
|
|
23
|
+
<div class="form-row">
|
|
24
|
+
<label for="node-input-engine"><i class="fa fa-tag"></i> Engine-URL</label>
|
|
25
|
+
<input type="text" id="node-input-engine" placeholder="http://engine:8000" />
|
|
26
|
+
</div>
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<script type="text/markdown" data-help-name="processdefinition-deploy">
|
|
30
|
+
A node to deploy process definition to the ProcessCube Engine.
|
|
31
|
+
|
|
32
|
+
## Inputs
|
|
33
|
+
|
|
34
|
+
: payload (String) : XML of the bpmn file
|
|
35
|
+
|
|
36
|
+
## Outputs
|
|
37
|
+
|
|
38
|
+
: payload (String) : XML of the bpmn file
|
|
39
|
+
|
|
40
|
+
### References
|
|
41
|
+
|
|
42
|
+
- [The ProcessCube© Developer Network](https://processcube.io) - All documentation for the ProcessCube© platform
|
|
43
|
+
- [ProcessCube© LowCode Integration](https://processcube.io/docs/node-red) - LowCode integration in ProcessCube©
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
function ProcessdefinitionDeploy(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
var node = this;
|
|
5
|
+
|
|
6
|
+
node.on('input', function (msg) {
|
|
7
|
+
node.engine = RED.nodes.getNode(config.engine);
|
|
8
|
+
const client = node.engine.engineClient;
|
|
9
|
+
const isUser = !!msg._client?.user && !!msg._client.user.accessToken;
|
|
10
|
+
const userIdentity = isUser ? { userId: msg._client.user.id, token: msg._client.user.accessToken } : null;
|
|
11
|
+
|
|
12
|
+
if (!client) {
|
|
13
|
+
node.error('No engine configured.');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
client.processDefinitions
|
|
18
|
+
.persistProcessDefinitions(msg.payload, { overwriteExisting: true, identity: userIdentity })
|
|
19
|
+
.then(() => {
|
|
20
|
+
node.send(msg);
|
|
21
|
+
})
|
|
22
|
+
.catch((error) => {
|
|
23
|
+
node.error(error, {});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
RED.nodes.registerType('processdefinition-deploy', ProcessdefinitionDeploy);
|
|
28
|
+
};
|