emailengine-app 2.68.1 → 2.69.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/.github/workflows/deploy.yml +2 -0
- package/.github/workflows/release.yaml +4 -0
- package/CHANGELOG.md +40 -0
- package/config/default.toml +2 -0
- package/data/google-crawlers.json +7 -1
- package/lib/account.js +62 -25
- package/lib/api-routes/account-routes.js +493 -75
- package/lib/api-routes/blocklist-routes.js +337 -0
- package/lib/api-routes/delivery-test-routes.js +321 -0
- package/lib/api-routes/export-routes.js +1 -12
- package/lib/api-routes/gateway-routes.js +376 -0
- package/lib/api-routes/license-routes.js +142 -0
- package/lib/api-routes/mailbox-routes.js +318 -0
- package/lib/api-routes/message-routes.js +21 -129
- package/lib/api-routes/oauth2-app-routes.js +631 -0
- package/lib/api-routes/outbox-routes.js +173 -0
- package/lib/api-routes/pubsub-routes.js +98 -0
- package/lib/api-routes/route-helpers.js +45 -0
- package/lib/api-routes/settings-routes.js +331 -0
- package/lib/api-routes/stats-routes.js +77 -0
- package/lib/api-routes/submit-routes.js +472 -0
- package/lib/api-routes/template-routes.js +7 -55
- package/lib/api-routes/token-routes.js +297 -0
- package/lib/api-routes/webhook-route-routes.js +152 -0
- package/lib/email-client/gmail-client.js +14 -0
- package/lib/email-client/imap/mailbox.js +34 -11
- package/lib/email-client/imap/subconnection.js +20 -12
- package/lib/email-client/imap/sync-operations.js +130 -2
- package/lib/email-client/imap-client.js +116 -58
- package/lib/email-client/outlook-client.js +85 -13
- package/lib/export.js +60 -19
- package/lib/imapproxy/imap-core/lib/commands/starttls.js +18 -0
- package/lib/imapproxy/imap-core/lib/imap-command.js +6 -1
- package/lib/imapproxy/imap-core/lib/imap-connection.js +106 -23
- package/lib/imapproxy/imap-core/lib/imap-server.js +24 -0
- package/lib/imapproxy/imap-core/lib/imap-stream.js +26 -0
- package/lib/message-port-stream.js +113 -16
- package/lib/reject-worker-calls.js +42 -0
- package/lib/routes-ui.js +37 -8778
- package/lib/schemas.js +26 -1
- package/lib/tools.js +68 -0
- package/lib/ui-routes/account-routes.js +40 -210
- package/lib/ui-routes/admin-config-routes.js +913 -487
- package/lib/ui-routes/admin-entities-routes.js +1 -0
- package/lib/ui-routes/auth-routes.js +1339 -0
- package/lib/ui-routes/dashboard-routes.js +188 -0
- package/lib/ui-routes/document-store-routes.js +800 -0
- package/lib/ui-routes/export-routes.js +217 -0
- package/lib/ui-routes/internals-routes.js +354 -0
- package/lib/ui-routes/network-config-routes.js +759 -0
- package/lib/ui-routes/{oauth-routes.js → oauth-config-routes.js} +371 -91
- package/lib/ui-routes/route-helpers.js +316 -0
- package/lib/ui-routes/smtp-test-routes.js +236 -0
- package/lib/ui-routes/unsubscribe-routes.js +234 -0
- package/lib/webhook-request.js +36 -0
- package/package.json +8 -8
- package/sbom.json +1 -1
- package/server.js +214 -16
- package/static/licenses.html +12 -12
- package/translations/messages.pot +129 -149
- package/views/dashboard.hbs +7 -26
- package/views/internals/index.hbs +15 -0
- package/views/tokens/index.hbs +9 -0
- package/workers/api.js +198 -4401
- package/workers/export.js +87 -54
- package/workers/imap.js +29 -13
- package/workers/submit.js +20 -11
- package/workers/webhooks.js +6 -20
|
@@ -1,34 +1,53 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// NB! This file is processed by the gettext parser (npm run gettext) and can not use newer syntax like ?.
|
|
4
|
+
|
|
5
|
+
// Admin UI routes for the remaining /admin/config/* pages: webhooks, service URL/branding,
|
|
6
|
+
// AI (OpenAI) settings, logging, and license. Extracted verbatim from lib/routes-ui.js.
|
|
7
|
+
// The notificationTypes / configWebhooksSchema / configLoggingSchema consts, the
|
|
8
|
+
// getOpenAiError helper, and the getDefaultPrompt helper move with the routes (only these
|
|
9
|
+
// config pages use them).
|
|
10
|
+
|
|
3
11
|
const Joi = require('joi');
|
|
4
12
|
const crypto = require('crypto');
|
|
13
|
+
const config = require('@zone-eu/wild-config');
|
|
14
|
+
const libmime = require('libmime');
|
|
5
15
|
const he = require('he');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
const packageData = require('../../package.json');
|
|
6
18
|
const { simpleParser } = require('mailparser');
|
|
7
|
-
const
|
|
19
|
+
const { fetch: fetchCmd } = require('undici');
|
|
8
20
|
|
|
9
21
|
const settings = require('../settings');
|
|
10
|
-
const
|
|
22
|
+
const consts = require('../consts');
|
|
11
23
|
const getSecret = require('../get-secret');
|
|
24
|
+
const timezonesList = require('timezones-list').default;
|
|
25
|
+
const { redis, submitQueue, notifyQueue, documentsQueue } = require('../db');
|
|
26
|
+
const { getByteSize, formatByteSize, getDuration, failAction, hasEnvValue, readEnvValue, httpAgent } = require('../tools');
|
|
12
27
|
const { llmPreProcess } = require('../llm-pre-process');
|
|
13
28
|
const { locales } = require('../translations');
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const timezonesList = require('timezones-list').default;
|
|
29
|
+
const { settingsSchema } = require('../schemas');
|
|
30
|
+
const { getOpenAiModels, OPEN_AI_MODELS, getExampleDocumentsPayloads } = require('./route-helpers');
|
|
17
31
|
|
|
18
|
-
const {
|
|
32
|
+
const { REDIS_PREFIX, DEFAULT_MAX_LOG_LINES, DEFAULT_DELIVERY_ATTEMPTS, DEFAULT_MAX_BODY_SIZE, DEFAULT_MAX_PAYLOAD_TIMEOUT, NONCE_BYTES } = consts;
|
|
19
33
|
|
|
20
|
-
const
|
|
34
|
+
const LICENSE_HOST = 'https://postalsys.com';
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
config.api = config.api || {
|
|
37
|
+
port: 3000,
|
|
38
|
+
host: '127.0.0.1',
|
|
39
|
+
proxy: false
|
|
40
|
+
};
|
|
24
41
|
|
|
25
|
-
const
|
|
42
|
+
const MAX_BODY_SIZE = getByteSize(readEnvValue('EENGINE_MAX_BODY_SIZE') || config.api.maxBodySize) || DEFAULT_MAX_BODY_SIZE;
|
|
43
|
+
const MAX_PAYLOAD_TIMEOUT = getDuration(readEnvValue('EENGINE_MAX_PAYLOAD_TIMEOUT') || config.api.maxPayloadTimeout) || DEFAULT_MAX_PAYLOAD_TIMEOUT;
|
|
26
44
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
const ADMIN_ACCESS_ADDRESSES = hasEnvValue('EENGINE_ADMIN_ACCESS_ADDRESSES')
|
|
46
|
+
? readEnvValue('EENGINE_ADMIN_ACCESS_ADDRESSES')
|
|
47
|
+
.split(',')
|
|
48
|
+
.map(v => v.trim())
|
|
49
|
+
.filter(v => v)
|
|
50
|
+
: null;
|
|
32
51
|
|
|
33
52
|
const IMAP_INDEXERS = [
|
|
34
53
|
{
|
|
@@ -55,30 +74,25 @@ const notificationTypes = Object.keys(consts)
|
|
|
55
74
|
description: consts[`${key}_DESCRIPTION`]
|
|
56
75
|
}));
|
|
57
76
|
|
|
58
|
-
const ADMIN_ACCESS_ADDRESSES = hasEnvValue('EENGINE_ADMIN_ACCESS_ADDRESSES')
|
|
59
|
-
? readEnvValue('EENGINE_ADMIN_ACCESS_ADDRESSES')
|
|
60
|
-
.split(',')
|
|
61
|
-
.map(v => v.trim())
|
|
62
|
-
.filter(v => v)
|
|
63
|
-
: null;
|
|
64
|
-
|
|
65
|
-
const MAX_BODY_SIZE = getByteSize(readEnvValue('EENGINE_MAX_BODY_SIZE')) || consts.DEFAULT_MAX_BODY_SIZE;
|
|
66
|
-
const MAX_PAYLOAD_TIMEOUT = getDuration(readEnvValue('EENGINE_MAX_PAYLOAD_TIMEOUT')) || consts.DEFAULT_MAX_PAYLOAD_TIMEOUT;
|
|
67
|
-
|
|
68
|
-
// Validation schemas
|
|
69
77
|
const configWebhooksSchema = {
|
|
70
|
-
webhooksEnabled: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
78
|
+
webhooksEnabled: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false).description('Enable Webhooks'),
|
|
71
79
|
webhooks: Joi.string()
|
|
72
|
-
.uri({
|
|
80
|
+
.uri({
|
|
81
|
+
scheme: ['http', 'https'],
|
|
82
|
+
allowRelative: false
|
|
83
|
+
})
|
|
73
84
|
.allow('')
|
|
74
|
-
.example('https://myservice.com/imap/webhooks')
|
|
85
|
+
.example('https://myservice.com/imap/webhooks')
|
|
86
|
+
.description('Webhook URL'),
|
|
75
87
|
notifyAll: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
76
88
|
headersAll: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
77
89
|
notifyHeaders: Joi.string().empty('').trim(),
|
|
78
90
|
notifyText: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
79
91
|
notifyWebSafeHtml: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
92
|
+
|
|
80
93
|
notifyTextSize: Joi.alternatives().try(
|
|
81
94
|
Joi.number().empty('').integer().min(0),
|
|
95
|
+
// If it's a string, parse and convert it to bytes
|
|
82
96
|
Joi.string().custom((value, helpers) => {
|
|
83
97
|
let nr = getByteSize(value);
|
|
84
98
|
if (typeof nr !== 'number' || nr < 0) {
|
|
@@ -87,11 +101,14 @@ const configWebhooksSchema = {
|
|
|
87
101
|
return nr;
|
|
88
102
|
}, 'Byte size conversion')
|
|
89
103
|
),
|
|
104
|
+
|
|
90
105
|
notifyCalendarEvents: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
91
106
|
inboxNewOnly: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
107
|
+
|
|
92
108
|
notifyAttachments: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
93
109
|
notifyAttachmentSize: Joi.alternatives().try(
|
|
94
110
|
Joi.number().empty('').integer().min(0),
|
|
111
|
+
// If it's a string, parse and convert it to bytes
|
|
95
112
|
Joi.string().custom((value, helpers) => {
|
|
96
113
|
let nr = getByteSize(value);
|
|
97
114
|
if (typeof nr !== 'number' || nr < 0) {
|
|
@@ -100,10 +117,12 @@ const configWebhooksSchema = {
|
|
|
100
117
|
return nr;
|
|
101
118
|
}, 'Byte size conversion')
|
|
102
119
|
),
|
|
120
|
+
|
|
103
121
|
customHeaders: Joi.string()
|
|
104
122
|
.allow('')
|
|
105
123
|
.trim()
|
|
106
124
|
.max(10 * 1024)
|
|
125
|
+
.description('Custom request headers')
|
|
107
126
|
};
|
|
108
127
|
|
|
109
128
|
for (let type of notificationTypes) {
|
|
@@ -111,47 +130,25 @@ for (let type of notificationTypes) {
|
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
const configLoggingSchema = {
|
|
114
|
-
all: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
133
|
+
all: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false).description('Enable logs for all accounts'),
|
|
115
134
|
maxLogLines: Joi.number().integer().empty('').min(0).max(10000000).default(DEFAULT_MAX_LOG_LINES)
|
|
116
135
|
};
|
|
117
136
|
|
|
118
|
-
// Helper functions
|
|
119
|
-
async function getOpenAiModels(models, selectedModel) {
|
|
120
|
-
let modelList = (await settings.get('openAiModels')) || structuredClone(models);
|
|
121
|
-
|
|
122
|
-
if (selectedModel && !modelList.find(model => model.id === selectedModel)) {
|
|
123
|
-
modelList.unshift({ name: selectedModel, id: selectedModel });
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return modelList.map(model => {
|
|
127
|
-
model.selected = model.id === selectedModel;
|
|
128
|
-
return model;
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
137
|
async function getOpenAiError(gt) {
|
|
133
|
-
let
|
|
134
|
-
if (
|
|
138
|
+
let openAiError = await redis.get(`${REDIS_PREFIX}:openai:error`);
|
|
139
|
+
if (openAiError) {
|
|
135
140
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
openAiError = JSON.parse(openAiError);
|
|
142
|
+
switch (openAiError.code) {
|
|
143
|
+
case 'invalid_api_key':
|
|
144
|
+
openAiError.message = gt.gettext('Invalid API key for OpenAI');
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
138
147
|
} catch (err) {
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function getExampleDocumentsPayloads() {
|
|
146
|
-
const exampleDocumentsPayloads = require('../payload-examples-documents.json');
|
|
147
|
-
let examples = structuredClone(exampleDocumentsPayloads);
|
|
148
|
-
let serviceUrl = await settings.get('serviceUrl');
|
|
149
|
-
for (let example of examples) {
|
|
150
|
-
if (example.serviceUrl) {
|
|
151
|
-
example.serviceUrl = serviceUrl || example.serviceUrl;
|
|
148
|
+
openAiError = null;
|
|
152
149
|
}
|
|
153
150
|
}
|
|
154
|
-
return
|
|
151
|
+
return openAiError;
|
|
155
152
|
}
|
|
156
153
|
|
|
157
154
|
function init(args) {
|
|
@@ -162,7 +159,6 @@ function init(args) {
|
|
|
162
159
|
cmd: 'openAiDefaultPrompt'
|
|
163
160
|
});
|
|
164
161
|
|
|
165
|
-
// Webhooks config routes
|
|
166
162
|
server.route({
|
|
167
163
|
method: 'GET',
|
|
168
164
|
path: '/admin/config/webhooks',
|
|
@@ -173,8 +169,10 @@ function init(args) {
|
|
|
173
169
|
const notifyWebSafeHtml = (await settings.get('notifyWebSafeHtml')) || false;
|
|
174
170
|
const notifyTextSize = Number(await settings.get('notifyTextSize')) || 0;
|
|
175
171
|
const notifyCalendarEvents = (await settings.get('notifyCalendarEvents')) || false;
|
|
172
|
+
|
|
176
173
|
const notifyAttachments = (await settings.get('notifyAttachments')) || false;
|
|
177
174
|
const notifyAttachmentSize = Number(await settings.get('notifyAttachmentSize')) || 0;
|
|
175
|
+
|
|
178
176
|
const inboxNewOnly = (await settings.get('inboxNewOnly')) || false;
|
|
179
177
|
const customHeaders = (await settings.get('webhooksCustomHeaders')) || [];
|
|
180
178
|
|
|
@@ -182,8 +180,10 @@ function init(args) {
|
|
|
182
180
|
let values = {
|
|
183
181
|
webhooksEnabled: webhooksEnabled !== null ? !!webhooksEnabled : false,
|
|
184
182
|
webhooks: (await settings.get('webhooks')) || '',
|
|
183
|
+
|
|
185
184
|
notifyAll: webhookEvents.includes('*'),
|
|
186
185
|
inboxNewOnly,
|
|
186
|
+
|
|
187
187
|
headersAll: notifyHeaders.includes('*'),
|
|
188
188
|
notifyHeaders: notifyHeaders
|
|
189
189
|
.filter(entry => entry !== '*')
|
|
@@ -193,8 +193,10 @@ function init(args) {
|
|
|
193
193
|
notifyWebSafeHtml,
|
|
194
194
|
notifyTextSize: notifyTextSize ? formatByteSize(notifyTextSize) : '',
|
|
195
195
|
notifyCalendarEvents,
|
|
196
|
+
|
|
196
197
|
notifyAttachments,
|
|
197
198
|
notifyAttachmentSize: notifyAttachmentSize ? formatByteSize(notifyAttachmentSize) : '',
|
|
199
|
+
|
|
198
200
|
customHeaders: []
|
|
199
201
|
.concat(customHeaders || [])
|
|
200
202
|
.map(entry => `${entry.key}: ${entry.value}`.trim())
|
|
@@ -207,14 +209,19 @@ function init(args) {
|
|
|
207
209
|
pageTitle: 'Webhooks',
|
|
208
210
|
menuConfig: true,
|
|
209
211
|
menuConfigWebhooks: true,
|
|
212
|
+
|
|
210
213
|
notificationTypes: notificationTypes.map(type =>
|
|
211
214
|
Object.assign({}, type, { checked: webhookEvents.includes(type.name), isMessageNew: type.name === 'messageNew' })
|
|
212
215
|
),
|
|
216
|
+
|
|
213
217
|
values,
|
|
218
|
+
|
|
214
219
|
webhookErrorFlag: await settings.get('webhookErrorFlag'),
|
|
215
220
|
documentStoreEnabled: (await settings.get('documentStoreEnabled')) || false
|
|
216
221
|
},
|
|
217
|
-
{
|
|
222
|
+
{
|
|
223
|
+
layout: 'app'
|
|
224
|
+
}
|
|
218
225
|
);
|
|
219
226
|
}
|
|
220
227
|
});
|
|
@@ -231,9 +238,15 @@ function init(args) {
|
|
|
231
238
|
.map(line => {
|
|
232
239
|
let sep = line.indexOf(':');
|
|
233
240
|
if (sep >= 0) {
|
|
234
|
-
return {
|
|
241
|
+
return {
|
|
242
|
+
key: line.substring(0, sep).trim(),
|
|
243
|
+
value: line.substring(sep + 1).trim()
|
|
244
|
+
};
|
|
235
245
|
}
|
|
236
|
-
return {
|
|
246
|
+
return {
|
|
247
|
+
key: line,
|
|
248
|
+
value: ''
|
|
249
|
+
};
|
|
237
250
|
});
|
|
238
251
|
|
|
239
252
|
const data = {
|
|
@@ -243,14 +256,18 @@ function init(args) {
|
|
|
243
256
|
notifyWebSafeHtml: request.payload.notifyWebSafeHtml,
|
|
244
257
|
notifyTextSize: request.payload.notifyTextSize || 0,
|
|
245
258
|
notifyCalendarEvents: request.payload.notifyCalendarEvents,
|
|
259
|
+
|
|
246
260
|
notifyAttachments: request.payload.notifyAttachments,
|
|
247
261
|
notifyAttachmentSize: request.payload.notifyAttachmentSize,
|
|
262
|
+
|
|
248
263
|
inboxNewOnly: request.payload.inboxNewOnly,
|
|
264
|
+
|
|
249
265
|
webhookEvents: notificationTypes.filter(type => !!request.payload[`notify_${type.name}`]).map(type => type.name),
|
|
250
266
|
notifyHeaders: (request.payload.notifyHeaders || '')
|
|
251
267
|
.split(/\r?\n/)
|
|
252
268
|
.map(line => line.toLowerCase().trim())
|
|
253
269
|
.filter(line => line),
|
|
270
|
+
|
|
254
271
|
webhooksCustomHeaders: customHeaders
|
|
255
272
|
};
|
|
256
273
|
|
|
@@ -267,10 +284,12 @@ function init(args) {
|
|
|
267
284
|
}
|
|
268
285
|
|
|
269
286
|
if (!data.webhooksEnabled) {
|
|
287
|
+
// clear error message (if exists)
|
|
270
288
|
await settings.clear('webhookErrorFlag');
|
|
271
289
|
}
|
|
272
290
|
|
|
273
291
|
await request.flash({ type: 'info', message: `Configuration updated` });
|
|
292
|
+
|
|
274
293
|
return h.redirect('/admin/config/webhooks');
|
|
275
294
|
} catch (err) {
|
|
276
295
|
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
@@ -282,21 +301,31 @@ function init(args) {
|
|
|
282
301
|
pageTitle: 'Webhooks',
|
|
283
302
|
menuConfig: true,
|
|
284
303
|
menuConfigWebhooks: true,
|
|
304
|
+
|
|
285
305
|
notificationTypes: notificationTypes.map(type =>
|
|
286
306
|
Object.assign({}, type, { checked: !!request.payload[`notify_${type.name}`], isMessageNew: type.name === 'messageNew' })
|
|
287
307
|
),
|
|
308
|
+
|
|
288
309
|
webhookErrorFlag: await settings.get('webhookErrorFlag'),
|
|
289
310
|
documentStoreEnabled: (await settings.get('documentStoreEnabled')) || false
|
|
290
311
|
},
|
|
291
|
-
{
|
|
312
|
+
{
|
|
313
|
+
layout: 'app'
|
|
314
|
+
}
|
|
292
315
|
);
|
|
293
316
|
}
|
|
294
317
|
},
|
|
295
318
|
options: {
|
|
296
319
|
validate: {
|
|
297
|
-
options: {
|
|
320
|
+
options: {
|
|
321
|
+
stripUnknown: true,
|
|
322
|
+
abortEarly: false,
|
|
323
|
+
convert: true
|
|
324
|
+
},
|
|
325
|
+
|
|
298
326
|
async failAction(request, h, err) {
|
|
299
327
|
let errors = {};
|
|
328
|
+
|
|
300
329
|
if (err.details) {
|
|
301
330
|
err.details.forEach(detail => {
|
|
302
331
|
if (!errors[detail.path]) {
|
|
@@ -315,6 +344,7 @@ function init(args) {
|
|
|
315
344
|
pageTitle: 'Webhooks',
|
|
316
345
|
menuConfig: true,
|
|
317
346
|
menuConfigWebhooks: true,
|
|
347
|
+
|
|
318
348
|
notificationTypes: notificationTypes.map(type =>
|
|
319
349
|
Object.assign({}, type, {
|
|
320
350
|
checked: !!request.payload[`notify_${type.name}`],
|
|
@@ -322,20 +352,24 @@ function init(args) {
|
|
|
322
352
|
error: errors[`notify_${type.name}`]
|
|
323
353
|
})
|
|
324
354
|
),
|
|
355
|
+
|
|
325
356
|
errors,
|
|
357
|
+
|
|
326
358
|
webhookErrorFlag: await settings.get('webhookErrorFlag'),
|
|
327
359
|
documentStoreEnabled: (await settings.get('documentStoreEnabled')) || false
|
|
328
360
|
},
|
|
329
|
-
{
|
|
361
|
+
{
|
|
362
|
+
layout: 'app'
|
|
363
|
+
}
|
|
330
364
|
)
|
|
331
365
|
.takeover();
|
|
332
366
|
},
|
|
367
|
+
|
|
333
368
|
payload: Joi.object(configWebhooksSchema)
|
|
334
369
|
}
|
|
335
370
|
}
|
|
336
371
|
});
|
|
337
372
|
|
|
338
|
-
// Service config routes
|
|
339
373
|
server.route({
|
|
340
374
|
method: 'GET',
|
|
341
375
|
path: '/admin/config/service',
|
|
@@ -347,22 +381,26 @@ function init(args) {
|
|
|
347
381
|
serviceSecret: (await settings.get('serviceSecret')) || null,
|
|
348
382
|
queueKeep: (await settings.get('queueKeep')) || 0,
|
|
349
383
|
deliveryAttempts: await settings.get('deliveryAttempts'),
|
|
384
|
+
|
|
350
385
|
imapIndexer: (await settings.get('imapIndexer')) || 'full',
|
|
386
|
+
|
|
351
387
|
pageBrandName: (await settings.get('pageBrandName')) || '',
|
|
352
388
|
templateHeader: (await settings.get('templateHeader')) || '',
|
|
353
389
|
templateHtmlHead: (await settings.get('templateHtmlHead')) || '',
|
|
354
390
|
scriptEnv: (await settings.get('scriptEnv')) || '',
|
|
355
391
|
enableTokens: !(await settings.get('disableTokens')),
|
|
356
392
|
enableApiProxy: (await settings.get('enableApiProxy')) || false,
|
|
393
|
+
|
|
357
394
|
trackClicks: await settings.get('trackClicks'),
|
|
358
395
|
trackOpens: await settings.get('trackOpens'),
|
|
396
|
+
|
|
359
397
|
resolveGmailCategories: (await settings.get('resolveGmailCategories')) || false,
|
|
360
398
|
enableOAuthTokensApi: (await settings.get('enableOAuthTokensApi')) || false,
|
|
399
|
+
|
|
361
400
|
ignoreMailCertErrors: (await settings.get('ignoreMailCertErrors')) || false,
|
|
401
|
+
|
|
362
402
|
locale: (await settings.get('locale')) || false,
|
|
363
|
-
timezone: (await settings.get('timezone')) || false
|
|
364
|
-
gmailExportBatchSize: (await settings.get('gmailExportBatchSize')) || DEFAULT_GMAIL_EXPORT_BATCH_SIZE,
|
|
365
|
-
outlookExportBatchSize: (await settings.get('outlookExportBatchSize')) || DEFAULT_OUTLOOK_EXPORT_BATCH_SIZE
|
|
403
|
+
timezone: (await settings.get('timezone')) || false
|
|
366
404
|
};
|
|
367
405
|
|
|
368
406
|
if (typeof values.trackClicks !== 'boolean') {
|
|
@@ -385,21 +423,27 @@ function init(args) {
|
|
|
385
423
|
menuConfigService: true,
|
|
386
424
|
encryption: await getSecret(),
|
|
387
425
|
locales: locales.map(locale => Object.assign({ selected: locale.locale === values.locale }, locale)),
|
|
426
|
+
|
|
388
427
|
timezones: timezonesList.map(entry => ({
|
|
389
428
|
name: entry.label,
|
|
390
429
|
timezone: entry.tzCode,
|
|
391
430
|
selected: entry.tzCode === values.timezone
|
|
392
431
|
})),
|
|
432
|
+
|
|
393
433
|
imapIndexers: structuredClone(IMAP_INDEXERS).map(entry => {
|
|
394
434
|
if (entry.id === values.imapIndexer) {
|
|
395
435
|
entry.selected = true;
|
|
396
436
|
}
|
|
397
437
|
return entry;
|
|
398
438
|
}),
|
|
439
|
+
|
|
399
440
|
adminAccessLimit: ADMIN_ACCESS_ADDRESSES && ADMIN_ACCESS_ADDRESSES.length,
|
|
441
|
+
|
|
400
442
|
values
|
|
401
443
|
},
|
|
402
|
-
{
|
|
444
|
+
{
|
|
445
|
+
layout: 'app'
|
|
446
|
+
}
|
|
403
447
|
);
|
|
404
448
|
}
|
|
405
449
|
});
|
|
@@ -426,9 +470,7 @@ function init(args) {
|
|
|
426
470
|
locale: request.payload.locale,
|
|
427
471
|
timezone: request.payload.timezone,
|
|
428
472
|
deliveryAttempts: request.payload.deliveryAttempts,
|
|
429
|
-
imapIndexer: request.payload.imapIndexer
|
|
430
|
-
gmailExportBatchSize: request.payload.gmailExportBatchSize,
|
|
431
|
-
outlookExportBatchSize: request.payload.outlookExportBatchSize
|
|
473
|
+
imapIndexer: request.payload.imapIndexer
|
|
432
474
|
};
|
|
433
475
|
|
|
434
476
|
if (request.payload.serviceUrl) {
|
|
@@ -441,6 +483,7 @@ function init(args) {
|
|
|
441
483
|
}
|
|
442
484
|
|
|
443
485
|
await request.flash({ type: 'info', message: `Configuration updated` });
|
|
486
|
+
|
|
444
487
|
return h.redirect('/admin/config/service');
|
|
445
488
|
} catch (err) {
|
|
446
489
|
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
@@ -454,28 +497,39 @@ function init(args) {
|
|
|
454
497
|
menuConfigService: true,
|
|
455
498
|
locales: locales.map(locale => Object.assign({ selected: locale.locale === request.payload.locale }, locale)),
|
|
456
499
|
encryption: await getSecret(),
|
|
500
|
+
|
|
457
501
|
timezones: timezonesList.map(entry => ({
|
|
458
502
|
name: entry.label,
|
|
459
503
|
timezone: entry.tzCode,
|
|
460
504
|
selected: entry.tzCode === request.payload.timezone
|
|
461
505
|
})),
|
|
506
|
+
|
|
462
507
|
imapIndexers: structuredClone(IMAP_INDEXERS).map(entry => {
|
|
463
508
|
if (entry.id === request.payload.imapIndexer) {
|
|
464
509
|
entry.selected = true;
|
|
465
510
|
}
|
|
466
511
|
return entry;
|
|
467
512
|
}),
|
|
513
|
+
|
|
468
514
|
adminAccessLimit: ADMIN_ACCESS_ADDRESSES && ADMIN_ACCESS_ADDRESSES.length
|
|
469
515
|
},
|
|
470
|
-
{
|
|
516
|
+
{
|
|
517
|
+
layout: 'app'
|
|
518
|
+
}
|
|
471
519
|
);
|
|
472
520
|
}
|
|
473
521
|
},
|
|
474
522
|
options: {
|
|
475
523
|
validate: {
|
|
476
|
-
options: {
|
|
524
|
+
options: {
|
|
525
|
+
stripUnknown: true,
|
|
526
|
+
abortEarly: false,
|
|
527
|
+
convert: true
|
|
528
|
+
},
|
|
529
|
+
|
|
477
530
|
async failAction(request, h, err) {
|
|
478
531
|
let errors = {};
|
|
532
|
+
|
|
479
533
|
if (err.details) {
|
|
480
534
|
err.details.forEach(detail => {
|
|
481
535
|
if (!errors[detail.path]) {
|
|
@@ -496,24 +550,31 @@ function init(args) {
|
|
|
496
550
|
menuConfigService: true,
|
|
497
551
|
locales: locales.map(locale => Object.assign({ selected: locale.locale === request.payload.locale }, locale)),
|
|
498
552
|
encryption: await getSecret(),
|
|
553
|
+
|
|
499
554
|
timezones: timezonesList.map(entry => ({
|
|
500
555
|
name: entry.label,
|
|
501
556
|
timezone: entry.tzCode,
|
|
502
557
|
selected: entry.tzCode === request.payload.timezone
|
|
503
558
|
})),
|
|
559
|
+
|
|
504
560
|
imapIndexers: structuredClone(IMAP_INDEXERS).map(entry => {
|
|
505
561
|
if (entry.id === request.payload.imapIndexer) {
|
|
506
562
|
entry.selected = true;
|
|
507
563
|
}
|
|
508
564
|
return entry;
|
|
509
565
|
}),
|
|
566
|
+
|
|
510
567
|
adminAccessLimit: ADMIN_ACCESS_ADDRESSES && ADMIN_ACCESS_ADDRESSES.length,
|
|
568
|
+
|
|
511
569
|
errors
|
|
512
570
|
},
|
|
513
|
-
{
|
|
571
|
+
{
|
|
572
|
+
layout: 'app'
|
|
573
|
+
}
|
|
514
574
|
)
|
|
515
575
|
.takeover();
|
|
516
576
|
},
|
|
577
|
+
|
|
517
578
|
payload: Joi.object({
|
|
518
579
|
serviceUrl: settingsSchema.serviceUrl,
|
|
519
580
|
serviceSecret: settingsSchema.serviceSecret,
|
|
@@ -529,143 +590,558 @@ function init(args) {
|
|
|
529
590
|
trackClicks: settingsSchema.trackClicks.default(false),
|
|
530
591
|
resolveGmailCategories: settingsSchema.resolveGmailCategories.default(false),
|
|
531
592
|
ignoreMailCertErrors: settingsSchema.ignoreMailCertErrors.default(false),
|
|
532
|
-
|
|
593
|
+
|
|
594
|
+
// Following options can only be changed via the UI
|
|
595
|
+
enableOAuthTokensApi: Joi.boolean()
|
|
596
|
+
.truthy('Y', 'true', '1', 'on')
|
|
597
|
+
.falsy('N', 'false', 0, '')
|
|
598
|
+
.description('If true, then allow using using the OAuth tokens API endpoint')
|
|
599
|
+
.default(false),
|
|
533
600
|
enableTokens: Joi.boolean().truthy('Y', 'true', '1', 'on').falsy('N', 'false', 0, '').default(false),
|
|
601
|
+
|
|
534
602
|
locale: settingsSchema.locale
|
|
535
603
|
.empty('')
|
|
536
604
|
.valid(...locales.map(locale => locale.locale))
|
|
537
605
|
.default('en'),
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
outlookExportBatchSize: settingsSchema.outlookExportBatchSize.default(DEFAULT_OUTLOOK_EXPORT_BATCH_SIZE)
|
|
606
|
+
|
|
607
|
+
timezone: settingsSchema.timezone.empty('')
|
|
541
608
|
})
|
|
542
609
|
}
|
|
543
610
|
}
|
|
544
611
|
});
|
|
545
612
|
|
|
546
|
-
// Service preview route
|
|
547
613
|
server.route({
|
|
548
|
-
method: '
|
|
549
|
-
path: '/admin/config/
|
|
614
|
+
method: 'GET',
|
|
615
|
+
path: '/admin/config/ai',
|
|
550
616
|
async handler(request, h) {
|
|
551
|
-
|
|
552
|
-
'
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
validate: {
|
|
563
|
-
options: { stripUnknown: true, abortEarly: false, convert: true },
|
|
564
|
-
async failAction(request, h, err) {
|
|
565
|
-
request.logger.error({ msg: 'Failed to process preview', err });
|
|
566
|
-
return h.redirect('/admin').takeover();
|
|
567
|
-
},
|
|
568
|
-
payload: Joi.object({
|
|
569
|
-
pageBrandName: settingsSchema.pageBrandName.default(''),
|
|
570
|
-
templateHeader: settingsSchema.templateHeader.default(''),
|
|
571
|
-
templateHtmlHead: settingsSchema.templateHtmlHead.default('')
|
|
572
|
-
})
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
});
|
|
617
|
+
const errorLog = ((await llmPreProcess.getErrorLog()) || []).map(entry => {
|
|
618
|
+
if (entry.error && typeof entry.error === 'string') {
|
|
619
|
+
entry.error = entry.error
|
|
620
|
+
.replace(/\r?\n/g, '\n')
|
|
621
|
+
.replace(/^\s+at\s+.*$/gm, '')
|
|
622
|
+
.replace(/\n+/g, '\n')
|
|
623
|
+
.trim()
|
|
624
|
+
.replace(/(evalmachine.<anonymous>:)(\d+)/, (o, p, n) => p + (Number(n) - 1));
|
|
625
|
+
}
|
|
626
|
+
return entry;
|
|
627
|
+
});
|
|
576
628
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
break;
|
|
586
|
-
case 'webhook-default':
|
|
587
|
-
await settings.clear('webhookErrorFlag');
|
|
588
|
-
break;
|
|
589
|
-
case 'webhook-route':
|
|
590
|
-
if (request.payload.entry) {
|
|
591
|
-
await redis.hdel(`${REDIS_PREFIX}wh:c`, `${request.payload.entry}:webhookErrorFlag`);
|
|
592
|
-
}
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
return { success: true };
|
|
596
|
-
},
|
|
597
|
-
options: {
|
|
598
|
-
validate: {
|
|
599
|
-
options: { stripUnknown: true, abortEarly: false, convert: true },
|
|
600
|
-
failAction,
|
|
601
|
-
payload: Joi.object({
|
|
602
|
-
alert: Joi.string().required().max(1024),
|
|
603
|
-
entry: Joi.string().empty('').max(1024).trim()
|
|
604
|
-
})
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
});
|
|
629
|
+
const values = {
|
|
630
|
+
generateEmailSummary: (await settings.get('generateEmailSummary')) || false,
|
|
631
|
+
openAiPrompt: ((await settings.get('openAiPrompt')) || '').toString(),
|
|
632
|
+
contentFnJson: JSON.stringify(
|
|
633
|
+
((await settings.get(`openAiPreProcessingFn`)) || '').toString() ||
|
|
634
|
+
`// Pass all emails
|
|
635
|
+
return true;`
|
|
636
|
+
),
|
|
608
637
|
|
|
609
|
-
|
|
610
|
-
server.route({
|
|
611
|
-
method: 'POST',
|
|
612
|
-
path: '/admin/config/service/clean',
|
|
613
|
-
async handler(request) {
|
|
614
|
-
let errors = [];
|
|
615
|
-
for (let queue of [submitQueue, notifyQueue, documentsQueue]) {
|
|
616
|
-
for (let type of ['failed', 'completed']) {
|
|
617
|
-
try {
|
|
618
|
-
await queue.clean(1000, 100000, type);
|
|
619
|
-
request.logger.trace({ msg: 'Queue cleaned', queue: queue.name, type });
|
|
620
|
-
} catch (err) {
|
|
621
|
-
request.logger.error({ msg: 'Failed to clean queue', queue: queue.name, type, err });
|
|
622
|
-
errors.push(err.message);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
638
|
+
openAiAPIUrl: ((await settings.get('openAiAPIUrl')) || '').toString(),
|
|
626
639
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
},
|
|
632
|
-
options: {
|
|
633
|
-
validate: {
|
|
634
|
-
options: { stripUnknown: true, abortEarly: false, convert: true }
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
});
|
|
640
|
+
openAiTemperature: ((await settings.get('openAiTemperature')) || '').toString(),
|
|
641
|
+
openAiTopP: ((await settings.get('openAiTopP')) || '').toString(),
|
|
642
|
+
openAiMaxTokens: ((await settings.get('openAiMaxTokens')) || '').toString()
|
|
643
|
+
};
|
|
638
644
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
method: 'GET',
|
|
642
|
-
path: '/admin/config/logging',
|
|
643
|
-
async handler(request, h) {
|
|
644
|
-
let values = (await settings.get('logs')) || {};
|
|
645
|
-
if (typeof values.maxLogLines === 'undefined') {
|
|
646
|
-
values.maxLogLines = DEFAULT_MAX_LOG_LINES;
|
|
645
|
+
if (!values.openAiPrompt.trim()) {
|
|
646
|
+
values.openAiPrompt = await getDefaultPrompt();
|
|
647
647
|
}
|
|
648
|
-
|
|
648
|
+
|
|
649
|
+
let hasOpenAiAPIKey = !!(await settings.get('openAiAPIKey'));
|
|
650
|
+
let openAiModel = await settings.get('openAiModel');
|
|
651
|
+
let openAiError = await getOpenAiError(request.app.gt);
|
|
649
652
|
|
|
650
653
|
return h.view(
|
|
651
|
-
'config/
|
|
654
|
+
'config/ai',
|
|
652
655
|
{
|
|
653
|
-
pageTitle: '
|
|
656
|
+
pageTitle: 'AI Processing',
|
|
654
657
|
menuConfig: true,
|
|
655
|
-
|
|
656
|
-
|
|
658
|
+
menuConfigAi: true,
|
|
659
|
+
|
|
660
|
+
errorLog,
|
|
661
|
+
defaultPromptJson: JSON.stringify({ prompt: await getDefaultPrompt() }),
|
|
662
|
+
|
|
663
|
+
values,
|
|
664
|
+
|
|
665
|
+
hasOpenAiAPIKey,
|
|
666
|
+
openAiError,
|
|
667
|
+
openAiModels: await getOpenAiModels(OPEN_AI_MODELS, openAiModel),
|
|
668
|
+
|
|
669
|
+
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}'),
|
|
670
|
+
examplePayloadsJson: JSON.stringify(
|
|
671
|
+
(await getExampleDocumentsPayloads()).map(entry =>
|
|
672
|
+
Object.assign({}, entry, { summary: undefined, riskAssessment: undefined, preview: undefined })
|
|
673
|
+
)
|
|
674
|
+
)
|
|
657
675
|
},
|
|
658
|
-
{
|
|
676
|
+
{
|
|
677
|
+
layout: 'app'
|
|
678
|
+
}
|
|
659
679
|
);
|
|
660
680
|
}
|
|
661
681
|
});
|
|
662
682
|
|
|
663
683
|
server.route({
|
|
664
684
|
method: 'POST',
|
|
665
|
-
path: '/admin/config/
|
|
685
|
+
path: '/admin/config/ai',
|
|
666
686
|
async handler(request, h) {
|
|
667
687
|
try {
|
|
668
|
-
|
|
688
|
+
let contentFn;
|
|
689
|
+
try {
|
|
690
|
+
if (request.payload.contentFnJson === '') {
|
|
691
|
+
contentFn = null;
|
|
692
|
+
} else {
|
|
693
|
+
contentFn = JSON.parse(request.payload.contentFnJson);
|
|
694
|
+
if (typeof contentFn !== 'string') {
|
|
695
|
+
throw new Error('Invalid Format');
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
} catch (err) {
|
|
699
|
+
err.details = {
|
|
700
|
+
contentFnJson: 'Invalid JSON'
|
|
701
|
+
};
|
|
702
|
+
throw err;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
let data = {
|
|
706
|
+
generateEmailSummary: request.payload.generateEmailSummary,
|
|
707
|
+
openAiModel: request.payload.openAiModel,
|
|
708
|
+
openAiAPIUrl: request.payload.openAiAPIUrl,
|
|
709
|
+
openAiPrompt: (request.payload.openAiPrompt || '').toString(),
|
|
710
|
+
openAiPreProcessingFn: contentFn,
|
|
711
|
+
openAiTemperature: request.payload.openAiTemperature,
|
|
712
|
+
openAiTopP: request.payload.openAiTopP,
|
|
713
|
+
openAiMaxTokens: request.payload.openAiMaxTokens
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
let defaultUserPrompt = await getDefaultPrompt();
|
|
717
|
+
|
|
718
|
+
if (!data.openAiPrompt.trim() || data.openAiPrompt.trim() === defaultUserPrompt.trim()) {
|
|
719
|
+
data.openAiPrompt = '';
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (typeof request.payload.openAiAPIKey === 'string') {
|
|
723
|
+
data.openAiAPIKey = request.payload.openAiAPIKey;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (typeof request.payload.openAiAPIUrl === 'string') {
|
|
727
|
+
data.openAiAPIUrl = request.payload.openAiAPIUrl;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
for (let key of Object.keys(data)) {
|
|
731
|
+
await settings.set(key, data[key]);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
await request.flash({ type: 'info', message: `Configuration updated` });
|
|
735
|
+
|
|
736
|
+
return h.redirect('/admin/config/ai');
|
|
737
|
+
} catch (err) {
|
|
738
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
739
|
+
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
740
|
+
|
|
741
|
+
let hasOpenAiAPIKey = !!(await settings.get('openAiAPIKey'));
|
|
742
|
+
let openAiError = await getOpenAiError(request.app.gt);
|
|
743
|
+
|
|
744
|
+
const errorLog = ((await llmPreProcess.getErrorLog()) || []).map(entry => {
|
|
745
|
+
if (entry.error && typeof entry.error === 'string') {
|
|
746
|
+
entry.error = entry.error
|
|
747
|
+
.replace(/\r?\n/g, '\n')
|
|
748
|
+
.replace(/^\s+at\s+.*$/gm, '')
|
|
749
|
+
.replace(/\n+/g, '\n')
|
|
750
|
+
.trim()
|
|
751
|
+
.replace(/(evalmachine.<anonymous>:)(\d+)/, (o, p, n) => p + (Number(n) - 1));
|
|
752
|
+
}
|
|
753
|
+
return entry;
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
return h.view(
|
|
757
|
+
'config/ai',
|
|
758
|
+
{
|
|
759
|
+
pageTitle: 'AI Processing',
|
|
760
|
+
menuConfig: true,
|
|
761
|
+
menuConfigAi: true,
|
|
762
|
+
|
|
763
|
+
errorLog,
|
|
764
|
+
defaultPromptJson: JSON.stringify({ prompt: await getDefaultPrompt() }),
|
|
765
|
+
|
|
766
|
+
hasOpenAiAPIKey,
|
|
767
|
+
openAiError,
|
|
768
|
+
openAiModels: await getOpenAiModels(OPEN_AI_MODELS, request.payload.openAiModel),
|
|
769
|
+
|
|
770
|
+
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}'),
|
|
771
|
+
examplePayloadsJson: JSON.stringify(
|
|
772
|
+
(await getExampleDocumentsPayloads()).map(entry =>
|
|
773
|
+
Object.assign({}, entry, { summary: undefined, riskAssessment: undefined, preview: undefined })
|
|
774
|
+
)
|
|
775
|
+
)
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
layout: 'app'
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
options: {
|
|
784
|
+
validate: {
|
|
785
|
+
options: {
|
|
786
|
+
stripUnknown: true,
|
|
787
|
+
abortEarly: false,
|
|
788
|
+
convert: true
|
|
789
|
+
},
|
|
790
|
+
|
|
791
|
+
async failAction(request, h, err) {
|
|
792
|
+
let errors = {};
|
|
793
|
+
|
|
794
|
+
if (err.details) {
|
|
795
|
+
err.details.forEach(detail => {
|
|
796
|
+
if (!errors[detail.path]) {
|
|
797
|
+
errors[detail.path] = detail.message;
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
803
|
+
request.logger.error({ msg: 'Failed to update configuration', err });
|
|
804
|
+
|
|
805
|
+
let hasOpenAiAPIKey = !!(await settings.get('openAiAPIKey'));
|
|
806
|
+
let openAiError = await getOpenAiError(request.app.gt);
|
|
807
|
+
|
|
808
|
+
const errorLog = ((await llmPreProcess.getErrorLog()) || []).map(entry => {
|
|
809
|
+
if (entry.error && typeof entry.error === 'string') {
|
|
810
|
+
entry.error = entry.error
|
|
811
|
+
.replace(/\r?\n/g, '\n')
|
|
812
|
+
.replace(/^\s+at\s+.*$/gm, '')
|
|
813
|
+
.replace(/\n+/g, '\n')
|
|
814
|
+
.trim()
|
|
815
|
+
.replace(/(evalmachine.<anonymous>:)(\d+)/, (o, p, n) => p + (Number(n) - 1));
|
|
816
|
+
}
|
|
817
|
+
return entry;
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
return h
|
|
821
|
+
.view(
|
|
822
|
+
'config/ai',
|
|
823
|
+
{
|
|
824
|
+
pageTitle: 'AI Processing',
|
|
825
|
+
menuConfig: true,
|
|
826
|
+
menuConfigAi: true,
|
|
827
|
+
|
|
828
|
+
errorLog,
|
|
829
|
+
defaultPromptJson: JSON.stringify({ prompt: await getDefaultPrompt() }),
|
|
830
|
+
|
|
831
|
+
hasOpenAiAPIKey,
|
|
832
|
+
openAiError,
|
|
833
|
+
openAiModels: await getOpenAiModels(OPEN_AI_MODELS, request.payload.openAiModel),
|
|
834
|
+
|
|
835
|
+
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}'),
|
|
836
|
+
examplePayloadsJson: JSON.stringify(
|
|
837
|
+
(await getExampleDocumentsPayloads()).map(entry =>
|
|
838
|
+
Object.assign({}, entry, { summary: undefined, riskAssessment: undefined, preview: undefined })
|
|
839
|
+
)
|
|
840
|
+
),
|
|
841
|
+
|
|
842
|
+
errors
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
layout: 'app'
|
|
846
|
+
}
|
|
847
|
+
)
|
|
848
|
+
.takeover();
|
|
849
|
+
},
|
|
850
|
+
|
|
851
|
+
payload: Joi.object({
|
|
852
|
+
generateEmailSummary: settingsSchema.generateEmailSummary.default(false),
|
|
853
|
+
openAiAPIKey: settingsSchema.openAiAPIKey.empty(''),
|
|
854
|
+
openAiModel: settingsSchema.openAiModel.empty(''),
|
|
855
|
+
|
|
856
|
+
openAiAPIUrl: settingsSchema.openAiAPIUrl.default(''),
|
|
857
|
+
|
|
858
|
+
openAiPrompt: settingsSchema.openAiPrompt.default(''),
|
|
859
|
+
|
|
860
|
+
contentFnJson: Joi.string()
|
|
861
|
+
.max(1024 * 1024)
|
|
862
|
+
.default('')
|
|
863
|
+
.allow('')
|
|
864
|
+
.trim()
|
|
865
|
+
.description('Filter function'),
|
|
866
|
+
|
|
867
|
+
openAiTemperature: settingsSchema.openAiTemperature.default(''),
|
|
868
|
+
openAiTopP: settingsSchema.openAiTopP.default(''),
|
|
869
|
+
openAiMaxTokens: settingsSchema.openAiMaxTokens.default('')
|
|
870
|
+
})
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
server.route({
|
|
876
|
+
method: 'POST',
|
|
877
|
+
path: '/admin/config/ai/test-prompt',
|
|
878
|
+
async handler(request) {
|
|
879
|
+
try {
|
|
880
|
+
request.logger.info({ msg: 'Prompt test' });
|
|
881
|
+
|
|
882
|
+
const parsed = await simpleParser(Buffer.from(request.payload.emailFile, 'base64'));
|
|
883
|
+
|
|
884
|
+
const response = {};
|
|
885
|
+
response.summary = await call({
|
|
886
|
+
cmd: 'generateSummary',
|
|
887
|
+
data: {
|
|
888
|
+
message: {
|
|
889
|
+
headers: parsed.headerLines.map(header => libmime.decodeHeader(header.line)),
|
|
890
|
+
attachments: parsed.attachments,
|
|
891
|
+
html: parsed.html,
|
|
892
|
+
text: parsed.text
|
|
893
|
+
},
|
|
894
|
+
openAiAPIKey: request.payload.openAiAPIKey,
|
|
895
|
+
openAiModel: request.payload.openAiModel,
|
|
896
|
+
openAiAPIUrl: request.payload.openAiAPIUrl,
|
|
897
|
+
openAiPrompt: request.payload.openAiPrompt,
|
|
898
|
+
openAiTemperature: request.payload.openAiTemperature,
|
|
899
|
+
openAiTopP: request.payload.openAiTopP,
|
|
900
|
+
openAiMaxTokens: request.payload.openAiMaxTokens
|
|
901
|
+
},
|
|
902
|
+
timeout: 2 * 60 * 1000
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// crux from olden times
|
|
906
|
+
for (let key of Object.keys(response.summary)) {
|
|
907
|
+
// remove meta keys from output
|
|
908
|
+
if (key.charAt(0) === '_' || response.summary[key] === '') {
|
|
909
|
+
delete response.summary[key];
|
|
910
|
+
}
|
|
911
|
+
if (key === 'riskAssessment') {
|
|
912
|
+
response.riskAssessment = response.summary.riskAssessment;
|
|
913
|
+
delete response.summary.riskAssessment;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return { success: true, response };
|
|
918
|
+
} catch (err) {
|
|
919
|
+
request.logger.error({ msg: 'Failed to test prompt', err });
|
|
920
|
+
return { success: false, error: err.message };
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
options: {
|
|
924
|
+
payload: {
|
|
925
|
+
maxBytes: MAX_BODY_SIZE,
|
|
926
|
+
timeout: MAX_PAYLOAD_TIMEOUT
|
|
927
|
+
},
|
|
928
|
+
validate: {
|
|
929
|
+
options: {
|
|
930
|
+
stripUnknown: true,
|
|
931
|
+
abortEarly: false,
|
|
932
|
+
convert: true
|
|
933
|
+
},
|
|
934
|
+
|
|
935
|
+
failAction,
|
|
936
|
+
|
|
937
|
+
payload: Joi.object({
|
|
938
|
+
emailFile: Joi.string().base64({ paddingRequired: false }).required(),
|
|
939
|
+
openAiAPIKey: settingsSchema.openAiAPIKey.empty(''),
|
|
940
|
+
openAiModel: settingsSchema.openAiModel.empty(''),
|
|
941
|
+
openAiAPIUrl: settingsSchema.openAiAPIUrl.default(''),
|
|
942
|
+
openAiPrompt: settingsSchema.openAiPrompt.default(''),
|
|
943
|
+
openAiTemperature: settingsSchema.openAiTemperature.empty(''),
|
|
944
|
+
openAiTopP: settingsSchema.openAiTopP.empty(''),
|
|
945
|
+
openAiMaxTokens: settingsSchema.openAiMaxTokens.empty('')
|
|
946
|
+
})
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
server.route({
|
|
952
|
+
method: 'POST',
|
|
953
|
+
path: '/admin/config/ai/reload-models',
|
|
954
|
+
async handler(request) {
|
|
955
|
+
try {
|
|
956
|
+
request.logger.info({ msg: 'Reload models' });
|
|
957
|
+
|
|
958
|
+
const { models } = await call({
|
|
959
|
+
cmd: 'openAiListModels',
|
|
960
|
+
data: {
|
|
961
|
+
openAiAPIKey: request.payload.openAiAPIKey,
|
|
962
|
+
openAiAPIUrl: request.payload.openAiAPIUrl
|
|
963
|
+
},
|
|
964
|
+
timeout: 2 * 60 * 1000
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
if (models && models.length) {
|
|
968
|
+
await settings.set('openAiModels', models);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return { success: true, models };
|
|
972
|
+
} catch (err) {
|
|
973
|
+
request.logger.error({ msg: 'Failed reloading OpenAI models', err });
|
|
974
|
+
return {
|
|
975
|
+
success: false,
|
|
976
|
+
error: err.message
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
options: {
|
|
981
|
+
validate: {
|
|
982
|
+
options: {
|
|
983
|
+
stripUnknown: true,
|
|
984
|
+
abortEarly: false,
|
|
985
|
+
convert: true
|
|
986
|
+
},
|
|
987
|
+
|
|
988
|
+
failAction,
|
|
989
|
+
|
|
990
|
+
payload: Joi.object({
|
|
991
|
+
openAiAPIKey: settingsSchema.openAiAPIKey.empty(''),
|
|
992
|
+
openAiAPIUrl: settingsSchema.openAiAPIUrl.default('')
|
|
993
|
+
})
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
server.route({
|
|
999
|
+
method: 'POST',
|
|
1000
|
+
path: '/admin/config/service/preview',
|
|
1001
|
+
async handler(request, h) {
|
|
1002
|
+
return h.view(
|
|
1003
|
+
'config/service-preview',
|
|
1004
|
+
{
|
|
1005
|
+
pageBrandName: request.payload.pageBrandName,
|
|
1006
|
+
embeddedTemplateHeader: request.payload.templateHeader,
|
|
1007
|
+
embeddedTemplateHtmlHead: request.payload.templateHtmlHead
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
layout: 'public'
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
1013
|
+
},
|
|
1014
|
+
options: {
|
|
1015
|
+
validate: {
|
|
1016
|
+
options: {
|
|
1017
|
+
stripUnknown: true,
|
|
1018
|
+
abortEarly: false,
|
|
1019
|
+
convert: true
|
|
1020
|
+
},
|
|
1021
|
+
|
|
1022
|
+
async failAction(request, h, err) {
|
|
1023
|
+
request.logger.error({ msg: 'Failed to process preview', err });
|
|
1024
|
+
return h.redirect('/admin').takeover();
|
|
1025
|
+
},
|
|
1026
|
+
|
|
1027
|
+
payload: Joi.object({
|
|
1028
|
+
pageBrandName: settingsSchema.pageBrandName.default(''),
|
|
1029
|
+
templateHeader: settingsSchema.templateHeader.default(''),
|
|
1030
|
+
templateHtmlHead: settingsSchema.templateHtmlHead.default('')
|
|
1031
|
+
})
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
server.route({
|
|
1037
|
+
method: 'POST',
|
|
1038
|
+
path: '/admin/config/clear-error',
|
|
1039
|
+
async handler(request) {
|
|
1040
|
+
switch (request.payload.alert) {
|
|
1041
|
+
case 'open-ai':
|
|
1042
|
+
await redis.del(`${REDIS_PREFIX}:openai:error`);
|
|
1043
|
+
break;
|
|
1044
|
+
|
|
1045
|
+
case 'webhook-default':
|
|
1046
|
+
await settings.clear('webhookErrorFlag');
|
|
1047
|
+
break;
|
|
1048
|
+
|
|
1049
|
+
case 'webhook-route':
|
|
1050
|
+
if (request.payload.entry) {
|
|
1051
|
+
await redis.hdel(`${REDIS_PREFIX}wh:c`, `${request.payload.entry}:webhookErrorFlag`);
|
|
1052
|
+
}
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
return { success: true };
|
|
1057
|
+
},
|
|
1058
|
+
options: {
|
|
1059
|
+
validate: {
|
|
1060
|
+
options: {
|
|
1061
|
+
stripUnknown: true,
|
|
1062
|
+
abortEarly: false,
|
|
1063
|
+
convert: true
|
|
1064
|
+
},
|
|
1065
|
+
|
|
1066
|
+
failAction,
|
|
1067
|
+
|
|
1068
|
+
payload: Joi.object({
|
|
1069
|
+
alert: Joi.string().required().max(1024),
|
|
1070
|
+
entry: Joi.string().empty('').max(1024).trim()
|
|
1071
|
+
})
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
server.route({
|
|
1077
|
+
method: 'POST',
|
|
1078
|
+
path: '/admin/config/service/clean',
|
|
1079
|
+
async handler(request) {
|
|
1080
|
+
let errors = [];
|
|
1081
|
+
|
|
1082
|
+
for (let queue of [submitQueue, notifyQueue, documentsQueue]) {
|
|
1083
|
+
for (let type of ['failed', 'completed']) {
|
|
1084
|
+
try {
|
|
1085
|
+
await queue.clean(1000, 100000, type);
|
|
1086
|
+
request.logger.trace({ msg: 'Queue cleaned', queue: queue.name, type });
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
request.logger.error({ msg: 'Failed to clean queue', queue: queue.name, type, err });
|
|
1089
|
+
errors.push(err.message);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (errors.length) {
|
|
1095
|
+
return { success: false, error: 'Cleaning failed for some queues' };
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return { success: true };
|
|
1099
|
+
},
|
|
1100
|
+
options: {
|
|
1101
|
+
validate: {
|
|
1102
|
+
options: {
|
|
1103
|
+
stripUnknown: true,
|
|
1104
|
+
abortEarly: false,
|
|
1105
|
+
convert: true
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
server.route({
|
|
1112
|
+
method: 'GET',
|
|
1113
|
+
path: '/admin/config/logging',
|
|
1114
|
+
async handler(request, h) {
|
|
1115
|
+
let values = (await settings.get('logs')) || {};
|
|
1116
|
+
|
|
1117
|
+
if (typeof values.maxLogLines === 'undefined') {
|
|
1118
|
+
values.maxLogLines = DEFAULT_MAX_LOG_LINES;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
values.accounts = [].concat(values.accounts || []).join('\n');
|
|
1122
|
+
|
|
1123
|
+
return h.view(
|
|
1124
|
+
'config/logging',
|
|
1125
|
+
{
|
|
1126
|
+
pageTitle: 'Logging',
|
|
1127
|
+
menuConfig: true,
|
|
1128
|
+
menuConfigLogging: true,
|
|
1129
|
+
|
|
1130
|
+
values
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
layout: 'app'
|
|
1134
|
+
}
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
server.route({
|
|
1140
|
+
method: 'POST',
|
|
1141
|
+
path: '/admin/config/logging',
|
|
1142
|
+
async handler(request, h) {
|
|
1143
|
+
try {
|
|
1144
|
+
const data = {
|
|
669
1145
|
logs: {
|
|
670
1146
|
all: !!request.payload.all,
|
|
671
1147
|
maxLogLines: request.payload.maxLogLines || 0
|
|
@@ -677,6 +1153,7 @@ function init(args) {
|
|
|
677
1153
|
}
|
|
678
1154
|
|
|
679
1155
|
await request.flash({ type: 'info', message: `Configuration updated` });
|
|
1156
|
+
|
|
680
1157
|
return h.redirect('/admin/config/logging');
|
|
681
1158
|
} catch (err) {
|
|
682
1159
|
await request.flash({ type: 'danger', message: `Couldn't save settings. Try again.` });
|
|
@@ -689,15 +1166,23 @@ function init(args) {
|
|
|
689
1166
|
menuConfig: true,
|
|
690
1167
|
menuConfigWebhooks: true
|
|
691
1168
|
},
|
|
692
|
-
{
|
|
1169
|
+
{
|
|
1170
|
+
layout: 'app'
|
|
1171
|
+
}
|
|
693
1172
|
);
|
|
694
1173
|
}
|
|
695
1174
|
},
|
|
696
1175
|
options: {
|
|
697
1176
|
validate: {
|
|
698
|
-
options: {
|
|
1177
|
+
options: {
|
|
1178
|
+
stripUnknown: true,
|
|
1179
|
+
abortEarly: false,
|
|
1180
|
+
convert: true
|
|
1181
|
+
},
|
|
1182
|
+
|
|
699
1183
|
async failAction(request, h, err) {
|
|
700
1184
|
let errors = {};
|
|
1185
|
+
|
|
701
1186
|
if (err.details) {
|
|
702
1187
|
err.details.forEach(detail => {
|
|
703
1188
|
if (!errors[detail.path]) {
|
|
@@ -716,18 +1201,21 @@ function init(args) {
|
|
|
716
1201
|
pageTitle: 'Logging',
|
|
717
1202
|
menuConfig: true,
|
|
718
1203
|
menuConfigWebhooks: true,
|
|
1204
|
+
|
|
719
1205
|
errors
|
|
720
1206
|
},
|
|
721
|
-
{
|
|
1207
|
+
{
|
|
1208
|
+
layout: 'app'
|
|
1209
|
+
}
|
|
722
1210
|
)
|
|
723
1211
|
.takeover();
|
|
724
1212
|
},
|
|
1213
|
+
|
|
725
1214
|
payload: Joi.object(configLoggingSchema)
|
|
726
1215
|
}
|
|
727
1216
|
}
|
|
728
1217
|
});
|
|
729
1218
|
|
|
730
|
-
// Logging reconnect route
|
|
731
1219
|
server.route({
|
|
732
1220
|
method: 'POST',
|
|
733
1221
|
path: '/admin/config/logging/reconnect',
|
|
@@ -744,7 +1232,10 @@ function init(args) {
|
|
|
744
1232
|
}
|
|
745
1233
|
}
|
|
746
1234
|
|
|
747
|
-
return {
|
|
1235
|
+
return {
|
|
1236
|
+
success: true,
|
|
1237
|
+
accounts: requested
|
|
1238
|
+
};
|
|
748
1239
|
} catch (err) {
|
|
749
1240
|
request.logger.error({ msg: 'Failed to request reconnect', err, accounts: request.payload.accounts });
|
|
750
1241
|
return { success: false, error: err.message };
|
|
@@ -752,16 +1243,26 @@ function init(args) {
|
|
|
752
1243
|
},
|
|
753
1244
|
options: {
|
|
754
1245
|
validate: {
|
|
755
|
-
options: {
|
|
1246
|
+
options: {
|
|
1247
|
+
stripUnknown: true,
|
|
1248
|
+
abortEarly: false,
|
|
1249
|
+
convert: true
|
|
1250
|
+
},
|
|
1251
|
+
|
|
756
1252
|
failAction,
|
|
1253
|
+
|
|
757
1254
|
payload: Joi.object({
|
|
758
|
-
accounts: Joi.array()
|
|
1255
|
+
accounts: Joi.array()
|
|
1256
|
+
.items(Joi.string().max(256))
|
|
1257
|
+
.default([])
|
|
1258
|
+
.example(['account-id-1', 'account-id-2'])
|
|
1259
|
+
.description('Request reconnect for listed accounts')
|
|
1260
|
+
.label('LoggedAccounts')
|
|
759
1261
|
})
|
|
760
1262
|
}
|
|
761
1263
|
}
|
|
762
1264
|
});
|
|
763
1265
|
|
|
764
|
-
// Webhooks test route
|
|
765
1266
|
server.route({
|
|
766
1267
|
method: 'POST',
|
|
767
1268
|
path: '/admin/config/webhooks/test',
|
|
@@ -773,7 +1274,11 @@ function init(args) {
|
|
|
773
1274
|
|
|
774
1275
|
const webhooks = request.payload.webhooks;
|
|
775
1276
|
if (!webhooks) {
|
|
776
|
-
return {
|
|
1277
|
+
return {
|
|
1278
|
+
success: false,
|
|
1279
|
+
target: webhooks,
|
|
1280
|
+
error: 'Webhook URL is not set'
|
|
1281
|
+
};
|
|
777
1282
|
}
|
|
778
1283
|
|
|
779
1284
|
let parsed = new URL(webhooks);
|
|
@@ -800,9 +1305,15 @@ function init(args) {
|
|
|
800
1305
|
.map(line => {
|
|
801
1306
|
let sep = line.indexOf(':');
|
|
802
1307
|
if (sep >= 0) {
|
|
803
|
-
return {
|
|
1308
|
+
return {
|
|
1309
|
+
key: line.substring(0, sep).trim(),
|
|
1310
|
+
value: line.substring(sep + 1).trim()
|
|
1311
|
+
};
|
|
804
1312
|
}
|
|
805
|
-
return {
|
|
1313
|
+
return {
|
|
1314
|
+
key: line,
|
|
1315
|
+
value: ''
|
|
1316
|
+
};
|
|
806
1317
|
});
|
|
807
1318
|
|
|
808
1319
|
customHeaders.forEach(header => {
|
|
@@ -813,6 +1324,7 @@ function init(args) {
|
|
|
813
1324
|
let duration;
|
|
814
1325
|
try {
|
|
815
1326
|
let res;
|
|
1327
|
+
|
|
816
1328
|
let serviceUrl = await settings.get('serviceUrl');
|
|
817
1329
|
|
|
818
1330
|
try {
|
|
@@ -825,7 +1337,9 @@ function init(args) {
|
|
|
825
1337
|
account: null,
|
|
826
1338
|
date: new Date().toISOString(),
|
|
827
1339
|
event: 'test',
|
|
828
|
-
data: {
|
|
1340
|
+
data: {
|
|
1341
|
+
nonce: crypto.randomBytes(NONCE_BYTES).toString('base64url')
|
|
1342
|
+
}
|
|
829
1343
|
}),
|
|
830
1344
|
headers,
|
|
831
1345
|
dispatcher: httpAgent.retry
|
|
@@ -842,194 +1356,151 @@ function init(args) {
|
|
|
842
1356
|
throw err;
|
|
843
1357
|
}
|
|
844
1358
|
|
|
845
|
-
return {
|
|
1359
|
+
return {
|
|
1360
|
+
success: true,
|
|
1361
|
+
target: webhooks,
|
|
1362
|
+
duration
|
|
1363
|
+
};
|
|
846
1364
|
} catch (err) {
|
|
847
1365
|
request.logger.error({ msg: 'Failed posting webhook', webhooks, event: 'test', err });
|
|
848
|
-
return {
|
|
1366
|
+
return {
|
|
1367
|
+
success: false,
|
|
1368
|
+
target: webhooks,
|
|
1369
|
+
duration,
|
|
1370
|
+
error: err.message,
|
|
1371
|
+
code: err.code
|
|
1372
|
+
};
|
|
849
1373
|
}
|
|
850
1374
|
},
|
|
851
1375
|
options: {
|
|
852
1376
|
tags: ['test'],
|
|
853
1377
|
validate: {
|
|
854
|
-
options: {
|
|
1378
|
+
options: {
|
|
1379
|
+
stripUnknown: true,
|
|
1380
|
+
abortEarly: false,
|
|
1381
|
+
convert: true
|
|
1382
|
+
},
|
|
1383
|
+
|
|
855
1384
|
failAction,
|
|
1385
|
+
|
|
856
1386
|
payload: Joi.object({
|
|
857
1387
|
webhooks: Joi.string()
|
|
858
|
-
.uri({
|
|
859
|
-
|
|
1388
|
+
.uri({
|
|
1389
|
+
scheme: ['http', 'https'],
|
|
1390
|
+
allowRelative: false
|
|
1391
|
+
})
|
|
1392
|
+
.allow('')
|
|
1393
|
+
.example('https://myservice.com/imap/webhooks')
|
|
1394
|
+
.description('Webhook URL'),
|
|
860
1395
|
customHeaders: Joi.string()
|
|
861
1396
|
.allow('')
|
|
862
1397
|
.trim()
|
|
863
1398
|
.max(10 * 1024)
|
|
864
|
-
.default('')
|
|
1399
|
+
.default('')
|
|
1400
|
+
.description('Custom request headers'),
|
|
865
1401
|
payload: Joi.string()
|
|
866
1402
|
.max(1024 * 1024)
|
|
867
1403
|
.empty('')
|
|
868
1404
|
.trim()
|
|
1405
|
+
.description('Example JSON payload')
|
|
869
1406
|
})
|
|
870
1407
|
}
|
|
871
1408
|
}
|
|
872
1409
|
});
|
|
873
1410
|
|
|
874
|
-
//
|
|
875
|
-
server.route({
|
|
876
|
-
method: 'GET',
|
|
877
|
-
path: '/admin/config/ai',
|
|
878
|
-
async handler(request, h) {
|
|
879
|
-
const errorLog = ((await llmPreProcess.getErrorLog()) || []).map(entry => {
|
|
880
|
-
if (entry.error && typeof entry.error === 'string') {
|
|
881
|
-
entry.error = entry.error
|
|
882
|
-
.replace(/\r?\n/g, '\n')
|
|
883
|
-
.replace(/^\s+at\s+.*$/gm, '')
|
|
884
|
-
.replace(/\n+/g, '\n')
|
|
885
|
-
.trim()
|
|
886
|
-
.replace(/(evalmachine.<anonymous>:)(\d+)/, (o, p, n) => p + (Number(n) - 1));
|
|
887
|
-
}
|
|
888
|
-
return entry;
|
|
889
|
-
});
|
|
1411
|
+
// Webhook, template, gateway, and token routes are in admin-entities-routes.js
|
|
890
1412
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
defaultPromptJson: JSON.stringify({ prompt: await getDefaultPrompt() }),
|
|
921
|
-
values,
|
|
922
|
-
hasOpenAiAPIKey,
|
|
923
|
-
openAiError,
|
|
924
|
-
openAiModels: await getOpenAiModels(OPEN_AI_MODELS, openAiModel),
|
|
925
|
-
scriptEnvJson: JSON.stringify((await settings.get('scriptEnv')) || '{}'),
|
|
926
|
-
examplePayloadsJson: JSON.stringify(
|
|
927
|
-
(await getExampleDocumentsPayloads()).map(entry =>
|
|
928
|
-
Object.assign({}, entry, { summary: undefined, riskAssessment: undefined, preview: undefined })
|
|
929
|
-
)
|
|
930
|
-
)
|
|
1413
|
+
server.route({
|
|
1414
|
+
method: 'GET',
|
|
1415
|
+
path: '/admin/config/license',
|
|
1416
|
+
async handler(request, h) {
|
|
1417
|
+
await call({ cmd: 'checkLicense' });
|
|
1418
|
+
|
|
1419
|
+
let subexp = await settings.get('subexp');
|
|
1420
|
+
let expiresDays;
|
|
1421
|
+
if (subexp && !(request.app.licenseInfo && request.app.licenseInfo.details && request.app.licenseInfo.details.lt)) {
|
|
1422
|
+
let delayMs = new Date(subexp) - Date.now();
|
|
1423
|
+
expiresDays = Math.max(Math.ceil(delayMs / (24 * 3600 * 1000)), 0);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
return h.view(
|
|
1427
|
+
'config/license',
|
|
1428
|
+
{
|
|
1429
|
+
pageTitle: 'License',
|
|
1430
|
+
menuLicense: true,
|
|
1431
|
+
hideLicenseWarning: true,
|
|
1432
|
+
menuConfig: true,
|
|
1433
|
+
menuConfigLicense: true,
|
|
1434
|
+
|
|
1435
|
+
subexp,
|
|
1436
|
+
expiresDays,
|
|
1437
|
+
|
|
1438
|
+
showLicenseText:
|
|
1439
|
+
!request.app.licenseInfo ||
|
|
1440
|
+
!request.app.licenseInfo.active ||
|
|
1441
|
+
(request.app.licenseInfo.details && request.app.licenseInfo.details.trial)
|
|
931
1442
|
},
|
|
932
|
-
{
|
|
1443
|
+
{
|
|
1444
|
+
layout: 'app'
|
|
1445
|
+
}
|
|
933
1446
|
);
|
|
934
1447
|
}
|
|
935
1448
|
});
|
|
936
1449
|
|
|
937
1450
|
server.route({
|
|
938
1451
|
method: 'POST',
|
|
939
|
-
path: '/admin/config/
|
|
1452
|
+
path: '/admin/config/license',
|
|
940
1453
|
async handler(request, h) {
|
|
941
1454
|
try {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
if (typeof contentFn !== 'string') {
|
|
949
|
-
throw new Error('Invalid Format');
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
} catch (err) {
|
|
953
|
-
err.details = { contentFnJson: 'Invalid JSON' };
|
|
1455
|
+
// update license
|
|
1456
|
+
const licenseInfo = await call({ cmd: 'updateLicense', license: request.payload.license });
|
|
1457
|
+
if (!licenseInfo) {
|
|
1458
|
+
let err = new Error('Failed to update license. Check license file contents.');
|
|
1459
|
+
err.statusCode = 403;
|
|
1460
|
+
err.details = { license: err.message };
|
|
954
1461
|
throw err;
|
|
955
1462
|
}
|
|
956
1463
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
openAiModel: request.payload.openAiModel,
|
|
960
|
-
openAiAPIUrl: request.payload.openAiAPIUrl,
|
|
961
|
-
openAiPrompt: (request.payload.openAiPrompt || '').toString(),
|
|
962
|
-
openAiPreProcessingFn: contentFn,
|
|
963
|
-
openAiTemperature: request.payload.openAiTemperature,
|
|
964
|
-
openAiTopP: request.payload.openAiTopP,
|
|
965
|
-
openAiMaxTokens: request.payload.openAiMaxTokens
|
|
966
|
-
};
|
|
967
|
-
|
|
968
|
-
let defaultUserPrompt = await getDefaultPrompt();
|
|
969
|
-
if (!data.openAiPrompt.trim() || data.openAiPrompt.trim() === defaultUserPrompt.trim()) {
|
|
970
|
-
data.openAiPrompt = '';
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
if (typeof request.payload.openAiAPIKey === 'string') {
|
|
974
|
-
data.openAiAPIKey = request.payload.openAiAPIKey;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
if (typeof request.payload.openAiAPIUrl === 'string') {
|
|
978
|
-
data.openAiAPIUrl = request.payload.openAiAPIUrl;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
for (let key of Object.keys(data)) {
|
|
982
|
-
await settings.set(key, data[key]);
|
|
1464
|
+
if (licenseInfo.active) {
|
|
1465
|
+
await request.flash({ type: 'info', message: `License activated` });
|
|
983
1466
|
}
|
|
984
1467
|
|
|
985
|
-
|
|
986
|
-
return h.redirect('/admin/config/ai');
|
|
1468
|
+
return h.redirect('/admin/config/license');
|
|
987
1469
|
} catch (err) {
|
|
988
|
-
await request.flash({ type: 'danger', message: `Couldn't
|
|
989
|
-
request.logger.error({ msg: 'Failed to
|
|
990
|
-
|
|
991
|
-
let hasOpenAiAPIKey = !!(await settings.get('openAiAPIKey'));
|
|
992
|
-
let openAiError = await getOpenAiError(request.app.gt);
|
|
993
|
-
|
|
994
|
-
const errorLog = ((await llmPreProcess.getErrorLog()) || []).map(entry => {
|
|
995
|
-
if (entry.error && typeof entry.error === 'string') {
|
|
996
|
-
entry.error = entry.error
|
|
997
|
-
.replace(/\r?\n/g, '\n')
|
|
998
|
-
.replace(/^\s+at\s+.*$/gm, '')
|
|
999
|
-
.replace(/\n+/g, '\n')
|
|
1000
|
-
.trim()
|
|
1001
|
-
.replace(/(evalmachine.<anonymous>:)(\d+)/, (o, p, n) => p + (Number(n) - 1));
|
|
1002
|
-
}
|
|
1003
|
-
return entry;
|
|
1004
|
-
});
|
|
1470
|
+
await request.flash({ type: 'danger', message: `Couldn't register license. Check the key and try again.` });
|
|
1471
|
+
request.logger.error({ msg: 'Failed to register license key', err });
|
|
1005
1472
|
|
|
1006
1473
|
return h.view(
|
|
1007
|
-
'config/
|
|
1474
|
+
'config/license',
|
|
1008
1475
|
{
|
|
1009
|
-
pageTitle: '
|
|
1476
|
+
pageTitle: 'License',
|
|
1010
1477
|
menuConfig: true,
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
(await getExampleDocumentsPayloads()).map(entry =>
|
|
1020
|
-
Object.assign({}, entry, { summary: undefined, riskAssessment: undefined, preview: undefined })
|
|
1021
|
-
)
|
|
1022
|
-
)
|
|
1478
|
+
menuConfigWebhooks: true,
|
|
1479
|
+
|
|
1480
|
+
errors: err.details,
|
|
1481
|
+
showLicenseText:
|
|
1482
|
+
(err.details && !!err.details.license) ||
|
|
1483
|
+
!request.app.licenseInfo ||
|
|
1484
|
+
!request.app.licenseInfo.active ||
|
|
1485
|
+
(request.app.licenseInfo.details && request.app.licenseInfo.details.trial)
|
|
1023
1486
|
},
|
|
1024
|
-
{
|
|
1487
|
+
{
|
|
1488
|
+
layout: 'app'
|
|
1489
|
+
}
|
|
1025
1490
|
);
|
|
1026
1491
|
}
|
|
1027
1492
|
},
|
|
1028
1493
|
options: {
|
|
1029
1494
|
validate: {
|
|
1030
|
-
options: {
|
|
1495
|
+
options: {
|
|
1496
|
+
stripUnknown: true,
|
|
1497
|
+
abortEarly: false,
|
|
1498
|
+
convert: true
|
|
1499
|
+
},
|
|
1500
|
+
|
|
1031
1501
|
async failAction(request, h, err) {
|
|
1032
1502
|
let errors = {};
|
|
1503
|
+
|
|
1033
1504
|
if (err.details) {
|
|
1034
1505
|
err.details.forEach(detail => {
|
|
1035
1506
|
if (!errors[detail.path]) {
|
|
@@ -1038,200 +1509,155 @@ return true;`
|
|
|
1038
1509
|
});
|
|
1039
1510
|
}
|
|
1040
1511
|
|
|
1041
|
-
await request.flash({ type: 'danger', message: `Couldn't
|
|
1042
|
-
request.logger.error({ msg: 'Failed to
|
|
1043
|
-
|
|
1044
|
-
let hasOpenAiAPIKey = !!(await settings.get('openAiAPIKey'));
|
|
1045
|
-
let openAiError = await getOpenAiError(request.app.gt);
|
|
1046
|
-
|
|
1047
|
-
const errorLog = ((await llmPreProcess.getErrorLog()) || []).map(entry => {
|
|
1048
|
-
if (entry.error && typeof entry.error === 'string') {
|
|
1049
|
-
entry.error = entry.error
|
|
1050
|
-
.replace(/\r?\n/g, '\n')
|
|
1051
|
-
.replace(/^\s+at\s+.*$/gm, '')
|
|
1052
|
-
.replace(/\n+/g, '\n')
|
|
1053
|
-
.trim()
|
|
1054
|
-
.replace(/(evalmachine.<anonymous>:)(\d+)/, (o, p, n) => p + (Number(n) - 1));
|
|
1055
|
-
}
|
|
1056
|
-
return entry;
|
|
1057
|
-
});
|
|
1512
|
+
await request.flash({ type: 'danger', message: `Couldn't register license. Check the key and try again.` });
|
|
1513
|
+
request.logger.error({ msg: 'Failed to register license key', err });
|
|
1058
1514
|
|
|
1059
1515
|
return h
|
|
1060
1516
|
.view(
|
|
1061
|
-
'config/
|
|
1517
|
+
'config/license',
|
|
1062
1518
|
{
|
|
1063
|
-
pageTitle: '
|
|
1519
|
+
pageTitle: 'License',
|
|
1064
1520
|
menuConfig: true,
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
(
|
|
1074
|
-
Object.assign({}, entry, { summary: undefined, riskAssessment: undefined, preview: undefined })
|
|
1075
|
-
)
|
|
1076
|
-
),
|
|
1077
|
-
errors
|
|
1521
|
+
menuConfigWebhooks: true,
|
|
1522
|
+
|
|
1523
|
+
errors,
|
|
1524
|
+
|
|
1525
|
+
showLicenseText:
|
|
1526
|
+
(errors && !!errors.license) ||
|
|
1527
|
+
!request.app.licenseInfo ||
|
|
1528
|
+
!request.app.licenseInfo.active ||
|
|
1529
|
+
(request.app.licenseInfo.details && request.app.licenseInfo.details.trial)
|
|
1078
1530
|
},
|
|
1079
|
-
{
|
|
1531
|
+
{
|
|
1532
|
+
layout: 'app'
|
|
1533
|
+
}
|
|
1080
1534
|
)
|
|
1081
1535
|
.takeover();
|
|
1082
1536
|
},
|
|
1537
|
+
|
|
1083
1538
|
payload: Joi.object({
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
.max(1024 * 1024)
|
|
1091
|
-
.default('')
|
|
1092
|
-
.allow('')
|
|
1093
|
-
.trim(),
|
|
1094
|
-
openAiTemperature: settingsSchema.openAiTemperature.default(''),
|
|
1095
|
-
openAiTopP: settingsSchema.openAiTopP.default(''),
|
|
1096
|
-
openAiMaxTokens: settingsSchema.openAiMaxTokens.default('')
|
|
1097
|
-
})
|
|
1539
|
+
license: Joi.string()
|
|
1540
|
+
.max(10 * 1024)
|
|
1541
|
+
.required()
|
|
1542
|
+
.example('-----BEGIN LICENSE-----\r\n...')
|
|
1543
|
+
.description('License file')
|
|
1544
|
+
}).label('RegisterLicense')
|
|
1098
1545
|
}
|
|
1099
1546
|
}
|
|
1100
1547
|
});
|
|
1101
1548
|
|
|
1102
|
-
// AI test prompt route
|
|
1103
1549
|
server.route({
|
|
1104
1550
|
method: 'POST',
|
|
1105
|
-
path: '/admin/config/
|
|
1106
|
-
async handler(request) {
|
|
1551
|
+
path: '/admin/config/license/delete',
|
|
1552
|
+
async handler(request, h) {
|
|
1107
1553
|
try {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
data: {
|
|
1116
|
-
message: {
|
|
1117
|
-
headers: parsed.headerLines.map(header => libmime.decodeHeader(header.line)),
|
|
1118
|
-
attachments: parsed.attachments,
|
|
1119
|
-
html: parsed.html,
|
|
1120
|
-
text: parsed.text
|
|
1121
|
-
},
|
|
1122
|
-
openAiAPIKey: request.payload.openAiAPIKey,
|
|
1123
|
-
openAiModel: request.payload.openAiModel,
|
|
1124
|
-
openAiAPIUrl: request.payload.openAiAPIUrl,
|
|
1125
|
-
openAiPrompt: request.payload.openAiPrompt,
|
|
1126
|
-
openAiTemperature: request.payload.openAiTemperature,
|
|
1127
|
-
openAiTopP: request.payload.openAiTopP,
|
|
1128
|
-
openAiMaxTokens: request.payload.openAiMaxTokens
|
|
1129
|
-
},
|
|
1130
|
-
timeout: 2 * 60 * 1000
|
|
1131
|
-
});
|
|
1132
|
-
|
|
1133
|
-
for (let key of Object.keys(response.summary)) {
|
|
1134
|
-
if (key.charAt(0) === '_' || response.summary[key] === '') {
|
|
1135
|
-
delete response.summary[key];
|
|
1136
|
-
}
|
|
1137
|
-
if (key === 'riskAssessment') {
|
|
1138
|
-
response.riskAssessment = response.summary.riskAssessment;
|
|
1139
|
-
delete response.summary.riskAssessment;
|
|
1140
|
-
}
|
|
1554
|
+
const licenseInfo = await call({ cmd: 'removeLicense' });
|
|
1555
|
+
if (!licenseInfo) {
|
|
1556
|
+
let err = new Error('Failed to clear license info');
|
|
1557
|
+
err.statusCode = 403;
|
|
1558
|
+
throw err;
|
|
1559
|
+
} else {
|
|
1560
|
+
await request.flash({ type: 'info', message: `License removed` });
|
|
1141
1561
|
}
|
|
1142
1562
|
|
|
1143
|
-
return
|
|
1563
|
+
return h.redirect('/admin/config/license');
|
|
1144
1564
|
} catch (err) {
|
|
1145
|
-
request.
|
|
1146
|
-
|
|
1565
|
+
await request.flash({ type: 'danger', message: `Couldn't remove license. Try again.` });
|
|
1566
|
+
request.logger.error({ msg: 'Failed to unregister license key', err, token: request.payload.token, remoteAddress: request.app.ip });
|
|
1567
|
+
return h.redirect('/admin/config/license');
|
|
1147
1568
|
}
|
|
1148
1569
|
},
|
|
1149
1570
|
options: {
|
|
1150
|
-
payload: { maxBytes: MAX_BODY_SIZE, timeout: MAX_PAYLOAD_TIMEOUT },
|
|
1151
1571
|
validate: {
|
|
1152
|
-
options: {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
}
|
|
1572
|
+
options: {
|
|
1573
|
+
stripUnknown: true,
|
|
1574
|
+
abortEarly: false,
|
|
1575
|
+
convert: true
|
|
1576
|
+
},
|
|
1577
|
+
|
|
1578
|
+
async failAction(request, h, err) {
|
|
1579
|
+
await request.flash({ type: 'danger', message: `Couldn't remove license. Try again.` });
|
|
1580
|
+
request.logger.error({ msg: 'Failed to unregister license key', err });
|
|
1581
|
+
|
|
1582
|
+
return h.redirect('/admin/config/license').takeover();
|
|
1583
|
+
},
|
|
1584
|
+
|
|
1585
|
+
payload: Joi.object({})
|
|
1164
1586
|
}
|
|
1165
1587
|
}
|
|
1166
1588
|
});
|
|
1167
1589
|
|
|
1168
|
-
// AI reload models route
|
|
1169
1590
|
server.route({
|
|
1170
1591
|
method: 'POST',
|
|
1171
|
-
path: '/admin/config/
|
|
1592
|
+
path: '/admin/config/license/trial',
|
|
1172
1593
|
async handler(request) {
|
|
1173
1594
|
try {
|
|
1174
|
-
|
|
1595
|
+
// provision new trial license
|
|
1175
1596
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1597
|
+
let headers = {
|
|
1598
|
+
'Content-Type': 'application/json',
|
|
1599
|
+
'User-Agent': `${packageData.name}/${packageData.version} (+${packageData.homepage})`
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
let res = await fetchCmd(`${LICENSE_HOST}/licenses/trial`, {
|
|
1603
|
+
method: 'post',
|
|
1604
|
+
body: JSON.stringify({
|
|
1605
|
+
version: packageData.version,
|
|
1606
|
+
app: '@postalsys/emailengine-app',
|
|
1607
|
+
hostname: os.hostname() || 'localhost',
|
|
1608
|
+
url: (await settings.get('serviceUrl')) || ''
|
|
1609
|
+
}),
|
|
1610
|
+
headers,
|
|
1611
|
+
dispatcher: httpAgent.retry
|
|
1183
1612
|
});
|
|
1184
1613
|
|
|
1185
|
-
if (
|
|
1186
|
-
|
|
1614
|
+
if (!res.ok) {
|
|
1615
|
+
let err = new Error(res.statusText || `Invalid response: ${res.status} ${res.statusText}`);
|
|
1616
|
+
err.statusCode = res.status;
|
|
1617
|
+
|
|
1618
|
+
try {
|
|
1619
|
+
err.response = await res.json();
|
|
1620
|
+
} catch (err) {
|
|
1621
|
+
// ignore
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
throw err;
|
|
1187
1625
|
}
|
|
1188
1626
|
|
|
1189
|
-
|
|
1190
|
-
} catch (err) {
|
|
1191
|
-
request.logger.error({ msg: 'Failed reloading OpenAI models', err });
|
|
1192
|
-
return { success: false, error: err.message };
|
|
1193
|
-
}
|
|
1194
|
-
},
|
|
1195
|
-
options: {
|
|
1196
|
-
validate: {
|
|
1197
|
-
options: { stripUnknown: true, abortEarly: false, convert: true },
|
|
1198
|
-
failAction,
|
|
1199
|
-
payload: Joi.object({
|
|
1200
|
-
openAiAPIKey: settingsSchema.openAiAPIKey.empty(''),
|
|
1201
|
-
openAiAPIUrl: settingsSchema.openAiAPIUrl.default('')
|
|
1202
|
-
})
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
});
|
|
1627
|
+
const data = await res.json();
|
|
1206
1628
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1629
|
+
let licenseFile = `-----BEGIN LICENSE-----
|
|
1630
|
+
${Buffer.from(data.content, 'base64url').toString('base64')}
|
|
1631
|
+
-----END LICENSE-----`;
|
|
1632
|
+
|
|
1633
|
+
const licenseInfo = await call({ cmd: 'updateLicense', license: licenseFile });
|
|
1634
|
+
if (!licenseInfo) {
|
|
1635
|
+
let err = new Error('Failed to update license. Check license file contents.');
|
|
1636
|
+
err.statusCode = 403;
|
|
1637
|
+
err.details = { license: err.message };
|
|
1638
|
+
throw err;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
if (licenseInfo.active) {
|
|
1642
|
+
await request.flash({ type: 'info', message: `Trial activated` });
|
|
1643
|
+
return { success: true, message: `Trial activated` };
|
|
1218
1644
|
}
|
|
1645
|
+
|
|
1646
|
+
throw new Error('Failed to activate provisioned trial license');
|
|
1647
|
+
} catch (err) {
|
|
1648
|
+
request.logger.error({ msg: 'Failed to provision a trial license key', err, remoteAddress: request.app.ip });
|
|
1649
|
+
return { success: false, error: (err.response && err.response.error) || err.message };
|
|
1219
1650
|
}
|
|
1220
|
-
return { success: true };
|
|
1221
1651
|
},
|
|
1222
1652
|
options: {
|
|
1223
1653
|
validate: {
|
|
1224
|
-
options: {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
.regex(/^[a-z0-9]{1,5}([-_][a-z0-9]{1,15})?$/)
|
|
1232
|
-
.allow(false),
|
|
1233
|
-
timezone: Joi.string().empty('').allow(false).max(255)
|
|
1234
|
-
})
|
|
1654
|
+
options: {
|
|
1655
|
+
stripUnknown: true,
|
|
1656
|
+
abortEarly: false,
|
|
1657
|
+
convert: true
|
|
1658
|
+
},
|
|
1659
|
+
|
|
1660
|
+
failAction
|
|
1235
1661
|
}
|
|
1236
1662
|
}
|
|
1237
1663
|
});
|