emailengine-app 2.61.5 → 2.62.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/CHANGELOG.md +78 -0
- package/data/google-crawlers.json +1 -1
- package/lib/account.js +20 -7
- package/lib/api-routes/account-routes.js +28 -5
- package/lib/api-routes/chat-routes.js +1 -1
- package/lib/api-routes/export-routes.js +316 -0
- package/lib/api-routes/message-routes.js +28 -23
- package/lib/api-routes/template-routes.js +28 -7
- package/lib/arf-detect.js +1 -1
- package/lib/consts.js +16 -0
- package/lib/db.js +3 -0
- package/lib/email-client/base-client.js +6 -4
- package/lib/email-client/gmail-client.js +204 -33
- package/lib/email-client/imap/mailbox.js +99 -8
- package/lib/email-client/imap/subconnection.js +5 -5
- package/lib/email-client/imap-client.js +76 -16
- package/lib/email-client/message-builder.js +3 -1
- package/lib/email-client/notification-handler.js +12 -9
- package/lib/email-client/outlook-client.js +362 -69
- package/lib/email-client/smtp-pool-manager.js +1 -1
- package/lib/export.js +528 -0
- package/lib/oauth/gmail.js +21 -13
- package/lib/oauth/mail-ru.js +23 -10
- package/lib/oauth/outlook.js +26 -16
- package/lib/oauth/pubsub/google.js +5 -0
- package/lib/routes-ui.js +235 -1
- package/lib/schemas.js +260 -80
- package/lib/stream-encrypt.js +263 -0
- package/lib/tools.js +30 -4
- package/lib/ui-routes/account-routes.js +23 -0
- package/lib/ui-routes/admin-config-routes.js +11 -4
- package/lib/ui-routes/admin-entities-routes.js +18 -0
- package/lib/webhooks.js +16 -20
- package/package.json +16 -16
- package/sbom.json +1 -1
- package/server.js +41 -5
- package/static/js/ace/ace.js +1 -1
- package/static/js/ace/ext-language_tools.js +1 -1
- package/static/licenses.html +52 -62
- package/translations/de.mo +0 -0
- package/translations/de.po +63 -36
- package/translations/en.mo +0 -0
- package/translations/en.po +64 -37
- package/translations/et.mo +0 -0
- package/translations/et.po +63 -36
- package/translations/fr.mo +0 -0
- package/translations/fr.po +63 -36
- package/translations/ja.mo +0 -0
- package/translations/ja.po +63 -36
- package/translations/messages.pot +80 -47
- package/translations/nl.mo +0 -0
- package/translations/nl.po +63 -36
- package/translations/pl.mo +0 -0
- package/translations/pl.po +63 -36
- package/views/accounts/account.hbs +375 -2
- package/views/config/service.hbs +35 -0
- package/workers/api.js +123 -44
- package/workers/documents.js +1 -0
- package/workers/export.js +926 -0
- package/workers/imap.js +29 -0
- package/workers/submit.js +25 -5
- package/workers/webhooks.js +11 -2
package/translations/pl.po
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
msgid ""
|
|
2
2
|
msgstr ""
|
|
3
3
|
"Project-Id-Version: \n"
|
|
4
|
-
"POT-Creation-Date: 2026-
|
|
4
|
+
"POT-Creation-Date: 2026-02-05 09:12+0000\n"
|
|
5
5
|
"PO-Revision-Date: \n"
|
|
6
6
|
"Last-Translator: \n"
|
|
7
7
|
"Language-Team: \n"
|
|
@@ -69,14 +69,6 @@ msgstr "Adres e-mail"
|
|
|
69
69
|
msgid "Enter your email address"
|
|
70
70
|
msgstr "Wprowadź swój adres e-mail"
|
|
71
71
|
|
|
72
|
-
#: views/accounts/register/index.hbs:2
|
|
73
|
-
msgid "Choose your email account provider"
|
|
74
|
-
msgstr "Wybierz dostawcę konta e-mail"
|
|
75
|
-
|
|
76
|
-
#: views/accounts/register/index.hbs:15
|
|
77
|
-
msgid "Standard IMAP"
|
|
78
|
-
msgstr "Standardowy IMAP"
|
|
79
|
-
|
|
80
72
|
#: views/accounts/register/imap.hbs:11
|
|
81
73
|
msgid "Your name"
|
|
82
74
|
msgstr "Twoje imię"
|
|
@@ -99,6 +91,14 @@ msgstr "Wprowadź hasło do konta"
|
|
|
99
91
|
msgid "Continue"
|
|
100
92
|
msgstr "Kontynuuj"
|
|
101
93
|
|
|
94
|
+
#: views/accounts/register/index.hbs:2
|
|
95
|
+
msgid "Choose your email account provider"
|
|
96
|
+
msgstr "Wybierz dostawcę konta e-mail"
|
|
97
|
+
|
|
98
|
+
#: views/accounts/register/index.hbs:15
|
|
99
|
+
msgid "Standard IMAP"
|
|
100
|
+
msgstr "Standardowy IMAP"
|
|
101
|
+
|
|
102
102
|
#: views/accounts/register/imap-server.hbs:19
|
|
103
103
|
msgid "IMAP"
|
|
104
104
|
msgstr "IMAP"
|
|
@@ -220,15 +220,15 @@ msgstr "Nie udalo sie polaczyc z serwerem SMTP"
|
|
|
220
220
|
msgid "Request failed."
|
|
221
221
|
msgstr "Zadanie nie powiodlo sie."
|
|
222
222
|
|
|
223
|
-
#: lib/routes-ui.js:
|
|
223
|
+
#: lib/routes-ui.js:520 lib/ui-routes/account-routes.js:60
|
|
224
224
|
msgid "Delegated"
|
|
225
225
|
msgstr "Delegowany"
|
|
226
226
|
|
|
227
|
-
#: lib/routes-ui.js:
|
|
227
|
+
#: lib/routes-ui.js:521 lib/ui-routes/account-routes.js:61
|
|
228
228
|
msgid "Using credentials from \"%s\""
|
|
229
229
|
msgstr "Używanie poświadczeń z \"%s\""
|
|
230
230
|
|
|
231
|
-
#: lib/routes-ui.js:
|
|
231
|
+
#: lib/routes-ui.js:571 lib/ui-routes/account-routes.js:111
|
|
232
232
|
msgid ""
|
|
233
233
|
"Connection timed out. This usually occurs if you are behind a firewall or "
|
|
234
234
|
"connecting to the wrong port."
|
|
@@ -236,11 +236,11 @@ msgstr ""
|
|
|
236
236
|
"Przekroczono limit czasu połączenia. Zwykle dzieje się tak, gdy użytkownik "
|
|
237
237
|
"znajduje się za zaporą sieciową lub łączy się z niewłaściwym portem."
|
|
238
238
|
|
|
239
|
-
#: lib/routes-ui.js:
|
|
239
|
+
#: lib/routes-ui.js:574 lib/ui-routes/account-routes.js:114
|
|
240
240
|
msgid "The server unexpectedly closed the connection."
|
|
241
241
|
msgstr "Serwer nieoczekiwanie zamknął połączenie."
|
|
242
242
|
|
|
243
|
-
#: lib/routes-ui.js:
|
|
243
|
+
#: lib/routes-ui.js:577 lib/ui-routes/account-routes.js:117
|
|
244
244
|
msgid ""
|
|
245
245
|
"The server unexpectedly closed the connection. This usually happens when "
|
|
246
246
|
"attempting to connect to a TLS port without TLS enabled."
|
|
@@ -248,7 +248,7 @@ msgstr ""
|
|
|
248
248
|
"Serwer nieoczekiwanie zamknął połączenie. Zwykle dzieje się tak podczas "
|
|
249
249
|
"próby połączenia z portem TLS bez włączonego protokołu TLS."
|
|
250
250
|
|
|
251
|
-
#: lib/routes-ui.js:
|
|
251
|
+
#: lib/routes-ui.js:582 lib/ui-routes/account-routes.js:122
|
|
252
252
|
msgid ""
|
|
253
253
|
"The server refused the connection. This typically occurs if the server is "
|
|
254
254
|
"not running, is overloaded, or you are connecting to the wrong host or port."
|
|
@@ -256,55 +256,82 @@ msgstr ""
|
|
|
256
256
|
"Serwer odmówił połączenia. Zwykle dzieje się tak, gdy serwer nie działa, "
|
|
257
257
|
"jest przeciążony lub łączysz się z niewłaściwym hostem lub portem."
|
|
258
258
|
|
|
259
|
-
#: lib/routes-ui.js:
|
|
259
|
+
#: lib/routes-ui.js:709
|
|
260
260
|
msgid "Invalid API key for OpenAI"
|
|
261
261
|
msgstr "Nieprawidłowy klucz API dla OpenAI"
|
|
262
262
|
|
|
263
|
-
#: lib/routes-ui.js:
|
|
264
|
-
#: lib/routes-ui.js:
|
|
265
|
-
#: workers/api.js:
|
|
266
|
-
#: lib/ui-routes/account-routes.js:
|
|
267
|
-
#: lib/ui-routes/account-routes.js:
|
|
268
|
-
#: lib/ui-routes/account-routes.js:
|
|
263
|
+
#: lib/routes-ui.js:4671 lib/routes-ui.js:4706 lib/routes-ui.js:4821
|
|
264
|
+
#: lib/routes-ui.js:4868 lib/routes-ui.js:5115 lib/routes-ui.js:5151
|
|
265
|
+
#: workers/api.js:2413 lib/ui-routes/account-routes.js:549
|
|
266
|
+
#: lib/ui-routes/account-routes.js:585 lib/ui-routes/account-routes.js:702
|
|
267
|
+
#: lib/ui-routes/account-routes.js:749 lib/ui-routes/account-routes.js:998
|
|
268
|
+
#: lib/ui-routes/account-routes.js:1034
|
|
269
269
|
msgid "Email Account Setup"
|
|
270
270
|
msgstr "Konfiguracja konta e-mail"
|
|
271
271
|
|
|
272
|
-
#: lib/routes-ui.js:
|
|
273
|
-
#: lib/ui-routes/account-routes.js:
|
|
272
|
+
#: lib/routes-ui.js:4731 lib/routes-ui.js:4764 lib/routes-ui.js:7701
|
|
273
|
+
#: lib/ui-routes/account-routes.js:610 lib/ui-routes/account-routes.js:644
|
|
274
274
|
msgid "Invalid request. Check your input and try again."
|
|
275
275
|
msgstr "Nie udało się zweryfikować argumentów żądania."
|
|
276
276
|
|
|
277
|
-
#: lib/routes-ui.js:
|
|
278
|
-
#: lib/ui-routes/account-routes.js:
|
|
277
|
+
#: lib/routes-ui.js:4924 lib/routes-ui.js:4935
|
|
278
|
+
#: lib/ui-routes/account-routes.js:806 lib/ui-routes/account-routes.js:817
|
|
279
279
|
#: lib/ui-routes/admin-entities-routes.js:2020
|
|
280
280
|
msgid "Server hostname was not found"
|
|
281
281
|
msgstr "Nie znaleziono nazwy hosta serwera"
|
|
282
282
|
|
|
283
|
-
#: lib/routes-ui.js:
|
|
284
|
-
#: lib/ui-routes/account-routes.js:
|
|
283
|
+
#: lib/routes-ui.js:4927 lib/routes-ui.js:4938
|
|
284
|
+
#: lib/ui-routes/account-routes.js:809 lib/ui-routes/account-routes.js:820
|
|
285
285
|
#: lib/ui-routes/admin-entities-routes.js:2023
|
|
286
286
|
msgid "Invalid username or password"
|
|
287
287
|
msgstr "Nieprawidłowa nazwa użytkownika lub hasło"
|
|
288
288
|
|
|
289
|
-
#: lib/routes-ui.js:
|
|
290
|
-
#: lib/ui-routes/admin-entities-routes.js:
|
|
289
|
+
#: lib/routes-ui.js:4941 lib/ui-routes/account-routes.js:823
|
|
290
|
+
#: lib/ui-routes/admin-entities-routes.js:2026
|
|
291
|
+
msgid "Authentication credentials were not provided"
|
|
292
|
+
msgstr "Nie podano danych uwierzytelniających"
|
|
293
|
+
|
|
294
|
+
#: lib/routes-ui.js:4944 lib/ui-routes/account-routes.js:826
|
|
295
|
+
#: lib/ui-routes/admin-entities-routes.js:2029
|
|
296
|
+
msgid "OAuth2 authentication failed"
|
|
297
|
+
msgstr "Uwierzytelnianie OAuth2 nie powiodło się"
|
|
298
|
+
|
|
299
|
+
#: lib/routes-ui.js:4947 lib/routes-ui.js:4951
|
|
300
|
+
#: lib/ui-routes/account-routes.js:829 lib/ui-routes/account-routes.js:833
|
|
301
|
+
#: lib/ui-routes/admin-entities-routes.js:2032
|
|
302
|
+
#: lib/ui-routes/admin-entities-routes.js:2036
|
|
291
303
|
msgid "TLS protocol error"
|
|
292
304
|
msgstr "Błąd protokołu TLS"
|
|
293
305
|
|
|
294
|
-
#: lib/routes-ui.js:
|
|
306
|
+
#: lib/routes-ui.js:4955 lib/ui-routes/account-routes.js:837
|
|
307
|
+
#: lib/ui-routes/admin-entities-routes.js:2040
|
|
308
|
+
msgid "Connection timed out"
|
|
309
|
+
msgstr "Przekroczono limit czasu połączenia"
|
|
310
|
+
|
|
311
|
+
#: lib/routes-ui.js:4958 lib/ui-routes/account-routes.js:840
|
|
312
|
+
#: lib/ui-routes/admin-entities-routes.js:2043
|
|
313
|
+
msgid "Could not connect to server"
|
|
314
|
+
msgstr "Nie można połączyć się z serwerem"
|
|
315
|
+
|
|
316
|
+
#: lib/routes-ui.js:4961 lib/ui-routes/account-routes.js:843
|
|
317
|
+
#: lib/ui-routes/admin-entities-routes.js:2046
|
|
318
|
+
msgid "Unexpected server response"
|
|
319
|
+
msgstr "Nieoczekiwana odpowiedź serwera"
|
|
320
|
+
|
|
321
|
+
#: lib/routes-ui.js:7664 lib/tools.js:778
|
|
295
322
|
msgid "Invalid input"
|
|
296
323
|
msgstr "Nieprawidłowe dane wejściowe"
|
|
297
324
|
|
|
298
|
-
#: lib/routes-ui.js:
|
|
299
|
-
#: lib/routes-ui.js:
|
|
325
|
+
#: lib/routes-ui.js:7674 lib/routes-ui.js:7792 lib/routes-ui.js:7809
|
|
326
|
+
#: lib/routes-ui.js:7845
|
|
300
327
|
msgid "Subscription Management"
|
|
301
328
|
msgstr "Zarządzanie subskrypcjami"
|
|
302
329
|
|
|
303
|
-
#: workers/api.js:
|
|
330
|
+
#: workers/api.js:6904 workers/api.js:7020
|
|
304
331
|
msgid "Requested page not found"
|
|
305
332
|
msgstr "Nie znaleziono żądanej strony"
|
|
306
333
|
|
|
307
|
-
#: workers/api.js:
|
|
334
|
+
#: workers/api.js:6905
|
|
308
335
|
msgid "Something went wrong"
|
|
309
336
|
msgstr "Ups... Coś poszlo nie tak"
|
|
310
337
|
|
|
@@ -328,7 +355,7 @@ msgstr ""
|
|
|
328
355
|
"365. Aby kontynuować, użyj przycisku \"Zaloguj się za pomocą Microsoft\", "
|
|
329
356
|
"aby bezpiecznie połączyć swoje konto."
|
|
330
357
|
|
|
331
|
-
#: lib/ui-routes/account-routes.js:
|
|
358
|
+
#: lib/ui-routes/account-routes.js:742 lib/ui-routes/account-routes.js:1027
|
|
332
359
|
msgid "Couldn't set up account. Try again."
|
|
333
360
|
msgstr "Nie udało się założyć konta. Spróbuj ponownie."
|
|
334
361
|
|
|
@@ -32,6 +32,14 @@
|
|
|
32
32
|
data-toggle="tooltip" data-placement="top">
|
|
33
33
|
<i class="fas fa-redo fa-fw" id="sync-icon"></i> Run sync
|
|
34
34
|
</button>
|
|
35
|
+
|
|
36
|
+
{{#if canReadMail}}
|
|
37
|
+
<button type="button" class="btn btn-light" id="request-export" title="Export messages to file"
|
|
38
|
+
data-toggle="modal" data-target="#exportModal">
|
|
39
|
+
<i class="fas fa-file-export fa-fw"></i> Export
|
|
40
|
+
<span id="export-status-indicator" class="badge badge-pill ml-1 d-none"></span>
|
|
41
|
+
</button>
|
|
42
|
+
{{/if}}
|
|
35
43
|
</div>
|
|
36
44
|
|
|
37
45
|
<div class="btn-group mr-2 mb-1" role="group" aria-label="Third group">
|
|
@@ -64,7 +72,7 @@
|
|
|
64
72
|
{{#if canUseSmtp}}
|
|
65
73
|
<a class="dropdown-item" data-toggle="modal" data-target="#testSendModal" data-keyboard="false"
|
|
66
74
|
data-backdrop="static" data-account="{{account.account}}" href="#">Account's
|
|
67
|
-
|
|
75
|
+
Mail server</a>
|
|
68
76
|
{{#if gateways}}
|
|
69
77
|
<div class="dropdown-divider"></div>
|
|
70
78
|
{{/if}}
|
|
@@ -935,6 +943,85 @@
|
|
|
935
943
|
</div>
|
|
936
944
|
</div>
|
|
937
945
|
|
|
946
|
+
<div class="modal fade" id="exportModal" tabindex="-1" aria-labelledby="exportModalLabel" aria-hidden="true">
|
|
947
|
+
<div class="modal-dialog">
|
|
948
|
+
<div class="modal-content">
|
|
949
|
+
<div class="modal-header">
|
|
950
|
+
<h5 class="modal-title" id="exportModalLabel">Export Messages</h5>
|
|
951
|
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
952
|
+
<span aria-hidden="true">×</span>
|
|
953
|
+
</button>
|
|
954
|
+
</div>
|
|
955
|
+
<div class="modal-body">
|
|
956
|
+
<div class="alert alert-info mb-3">
|
|
957
|
+
<i class="fas fa-flask fa-fw"></i>
|
|
958
|
+
<strong>Beta Feature:</strong> The export feature is currently in beta. If you encounter any issues,
|
|
959
|
+
please contact <a href="mailto:support@postalsys.com">support@postalsys.com</a>.
|
|
960
|
+
</div>
|
|
961
|
+
<div id="export-form-view">
|
|
962
|
+
<div class="form-group">
|
|
963
|
+
<label for="export-start-date">Start Date</label>
|
|
964
|
+
<input type="date" class="form-control" id="export-start-date">
|
|
965
|
+
</div>
|
|
966
|
+
<div class="form-group">
|
|
967
|
+
<label for="export-end-date">End Date</label>
|
|
968
|
+
<input type="date" class="form-control" id="export-end-date">
|
|
969
|
+
</div>
|
|
970
|
+
<div class="form-group">
|
|
971
|
+
<div class="custom-control custom-checkbox">
|
|
972
|
+
<input type="checkbox" class="custom-control-input" id="export-include-attachments">
|
|
973
|
+
<label class="custom-control-label" for="export-include-attachments">Include
|
|
974
|
+
attachments</label>
|
|
975
|
+
</div>
|
|
976
|
+
<small class="form-text text-muted">Download and include attachment file contents in the export.
|
|
977
|
+
This increases export size and time.</small>
|
|
978
|
+
</div>
|
|
979
|
+
<small class="text-muted">Gmail accounts export from All Mail. Other accounts export all folders
|
|
980
|
+
(excluding Junk and Trash).</small>
|
|
981
|
+
</div>
|
|
982
|
+
|
|
983
|
+
<div id="export-status-view" class="d-none">
|
|
984
|
+
<div class="mb-3">
|
|
985
|
+
<strong>Status:</strong>
|
|
986
|
+
<span id="export-status-badge" class="badge badge-info">Queued</span>
|
|
987
|
+
<span id="export-phase-badge" class="badge badge-secondary ml-1 d-none"></span>
|
|
988
|
+
</div>
|
|
989
|
+
<div class="mb-3">
|
|
990
|
+
<strong>Expires:</strong>
|
|
991
|
+
<span id="export-expires-at" class="text-muted"></span>
|
|
992
|
+
</div>
|
|
993
|
+
<div id="export-progress-section">
|
|
994
|
+
<div class="progress mb-2">
|
|
995
|
+
<div id="export-progress-bar"
|
|
996
|
+
class="progress-bar progress-bar-striped progress-bar-animated bg-info"
|
|
997
|
+
role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
|
|
998
|
+
style="width: 0%"></div>
|
|
999
|
+
</div>
|
|
1000
|
+
<small id="export-progress-text" class="text-muted"></small>
|
|
1001
|
+
</div>
|
|
1002
|
+
<div id="export-error-section" class="d-none">
|
|
1003
|
+
<div class="alert alert-danger" id="export-error-message"></div>
|
|
1004
|
+
</div>
|
|
1005
|
+
<div id="export-download-section" class="d-none mt-3">
|
|
1006
|
+
<a href="#" id="export-download-link" class="btn btn-success btn-block">
|
|
1007
|
+
<i class="fas fa-download fa-fw"></i> Download Export
|
|
1008
|
+
</a>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
</div>
|
|
1012
|
+
<div class="modal-footer">
|
|
1013
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
1014
|
+
<button type="button" class="btn btn-danger d-none" id="export-reset-btn">
|
|
1015
|
+
<i class="fas fa-trash fa-fw"></i> Reset
|
|
1016
|
+
</button>
|
|
1017
|
+
<button type="button" class="btn btn-primary" id="export-start-btn">
|
|
1018
|
+
<i class="fas fa-file-export fa-fw" id="export-start-icon"></i> Start Export
|
|
1019
|
+
</button>
|
|
1020
|
+
</div>
|
|
1021
|
+
</div>
|
|
1022
|
+
</div>
|
|
1023
|
+
</div>
|
|
1024
|
+
|
|
938
1025
|
<input type="hidden" id="account-id" value="{{account.account}}">
|
|
939
1026
|
<input type="hidden" id="crumb" value="{{crumb}}">
|
|
940
1027
|
|
|
@@ -1107,7 +1194,7 @@
|
|
|
1107
1194
|
flushingLogs = false;
|
|
1108
1195
|
showToast(data.error ? data.error : (data.success ? 'Stored logs were flushed' : 'Failed to flush logs'), data.success ? 'check-circle' : 'alert-triangle');
|
|
1109
1196
|
}).catch(err => {
|
|
1110
|
-
|
|
1197
|
+
flushingLogs = false;
|
|
1111
1198
|
showToast('Request failed\n' + err.message, 'alert-triangle');
|
|
1112
1199
|
})
|
|
1113
1200
|
});
|
|
@@ -1121,6 +1208,292 @@
|
|
|
1121
1208
|
|
|
1122
1209
|
$('#renew-grant-btn').tooltip();
|
|
1123
1210
|
$('#renew-grant-btn').click(() => $('#renew-grant-btn').tooltip('hide'));
|
|
1211
|
+
|
|
1212
|
+
// Export status indicator on page load
|
|
1213
|
+
async function updateExportIndicator() {
|
|
1214
|
+
const account = document.getElementById('account-id').value.trim();
|
|
1215
|
+
const indicator = document.getElementById('export-status-indicator');
|
|
1216
|
+
if (!indicator) return;
|
|
1217
|
+
|
|
1218
|
+
try {
|
|
1219
|
+
const res = await fetch(`/admin/accounts/${account}/exports?pageSize=1`);
|
|
1220
|
+
if (!res.ok) {
|
|
1221
|
+
indicator.classList.add('d-none');
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const data = await res.json();
|
|
1225
|
+
|
|
1226
|
+
if (data.exports && data.exports.length > 0) {
|
|
1227
|
+
const latest = data.exports[0];
|
|
1228
|
+
if (latest.status === 'queued' || latest.status === 'processing') {
|
|
1229
|
+
indicator.textContent = latest.status === 'queued' ? 'Queued' : 'Exporting';
|
|
1230
|
+
indicator.className = 'badge badge-pill badge-info ml-1';
|
|
1231
|
+
} else if (latest.status === 'completed') {
|
|
1232
|
+
indicator.textContent = 'Ready';
|
|
1233
|
+
indicator.className = 'badge badge-pill badge-success ml-1';
|
|
1234
|
+
} else {
|
|
1235
|
+
indicator.classList.add('d-none');
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
indicator.classList.remove('d-none');
|
|
1239
|
+
} else {
|
|
1240
|
+
indicator.classList.add('d-none');
|
|
1241
|
+
}
|
|
1242
|
+
} catch (err) {
|
|
1243
|
+
console.error('Failed to check export status', err);
|
|
1244
|
+
indicator.classList.add('d-none');
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// Check export status on page load
|
|
1249
|
+
updateExportIndicator();
|
|
1250
|
+
|
|
1251
|
+
// Export modal functionality
|
|
1252
|
+
let currentExportId = null;
|
|
1253
|
+
let exportPollingInterval = null;
|
|
1254
|
+
|
|
1255
|
+
function showFormView() {
|
|
1256
|
+
document.getElementById('export-form-view').classList.remove('d-none');
|
|
1257
|
+
document.getElementById('export-status-view').classList.add('d-none');
|
|
1258
|
+
document.getElementById('export-start-btn').classList.remove('d-none');
|
|
1259
|
+
document.getElementById('export-reset-btn').classList.add('d-none');
|
|
1260
|
+
|
|
1261
|
+
// Set default dates (last 1 year)
|
|
1262
|
+
const today = new Date();
|
|
1263
|
+
const lastYear = new Date(today);
|
|
1264
|
+
lastYear.setFullYear(lastYear.getFullYear() - 1);
|
|
1265
|
+
document.getElementById('export-end-date').value = today.toISOString().split('T')[0];
|
|
1266
|
+
document.getElementById('export-start-date').value = lastYear.toISOString().split('T')[0];
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function showStatusView() {
|
|
1270
|
+
document.getElementById('export-form-view').classList.add('d-none');
|
|
1271
|
+
document.getElementById('export-status-view').classList.remove('d-none');
|
|
1272
|
+
document.getElementById('export-start-btn').classList.add('d-none');
|
|
1273
|
+
document.getElementById('export-reset-btn').classList.remove('d-none');
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function getStatusBadgeClass(status) {
|
|
1277
|
+
switch (status) {
|
|
1278
|
+
case 'completed': return 'success';
|
|
1279
|
+
case 'failed': return 'danger';
|
|
1280
|
+
case 'processing': return 'info';
|
|
1281
|
+
default: return 'secondary';
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function startPolling() {
|
|
1286
|
+
if (exportPollingInterval) return;
|
|
1287
|
+
exportPollingInterval = setInterval(updateExportStatus, 3000);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function stopPolling() {
|
|
1291
|
+
if (exportPollingInterval) {
|
|
1292
|
+
clearInterval(exportPollingInterval);
|
|
1293
|
+
exportPollingInterval = null;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
async function updateExportStatus() {
|
|
1298
|
+
if (!currentExportId) return;
|
|
1299
|
+
|
|
1300
|
+
const account = document.getElementById('account-id').value.trim();
|
|
1301
|
+
try {
|
|
1302
|
+
const res = await fetch(`/admin/accounts/${account}/export/${currentExportId}`);
|
|
1303
|
+
if (!res.ok) {
|
|
1304
|
+
throw new Error(`HTTP error! status: ${res.status}`);
|
|
1305
|
+
}
|
|
1306
|
+
const data = await res.json();
|
|
1307
|
+
|
|
1308
|
+
// Update status badge
|
|
1309
|
+
const badge = document.getElementById('export-status-badge');
|
|
1310
|
+
badge.textContent = data.status.charAt(0).toUpperCase() + data.status.slice(1);
|
|
1311
|
+
badge.className = 'badge badge-' + getStatusBadgeClass(data.status);
|
|
1312
|
+
|
|
1313
|
+
// Update phase badge
|
|
1314
|
+
const phaseBadge = document.getElementById('export-phase-badge');
|
|
1315
|
+
if (data.phase && data.status === 'processing') {
|
|
1316
|
+
phaseBadge.textContent = data.phase.charAt(0).toUpperCase() + data.phase.slice(1);
|
|
1317
|
+
phaseBadge.classList.remove('d-none');
|
|
1318
|
+
} else {
|
|
1319
|
+
phaseBadge.classList.add('d-none');
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Update expiration date
|
|
1323
|
+
const expiresAtEl = document.getElementById('export-expires-at');
|
|
1324
|
+
if (data.expiresAt) {
|
|
1325
|
+
const expiresDate = new Date(data.expiresAt);
|
|
1326
|
+
expiresAtEl.textContent = expiresDate.toLocaleString();
|
|
1327
|
+
} else {
|
|
1328
|
+
expiresAtEl.textContent = 'Unknown';
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// Update progress bar
|
|
1332
|
+
const progressBar = document.getElementById('export-progress-bar');
|
|
1333
|
+
const progressText = document.getElementById('export-progress-text');
|
|
1334
|
+
|
|
1335
|
+
if (data.phase === 'indexing' && data.progress) {
|
|
1336
|
+
// During indexing, show folders scanned progress
|
|
1337
|
+
const pct = data.progress.foldersTotal > 0
|
|
1338
|
+
? Math.round((data.progress.foldersScanned / data.progress.foldersTotal) * 100)
|
|
1339
|
+
: 0;
|
|
1340
|
+
progressBar.style.width = pct + '%';
|
|
1341
|
+
progressBar.setAttribute('aria-valuenow', pct);
|
|
1342
|
+
progressText.textContent = `Scanning folders: ${data.progress.foldersScanned || 0} / ${data.progress.foldersTotal || '?'}`;
|
|
1343
|
+
} else if (data.progress) {
|
|
1344
|
+
// During exporting, show messages exported progress
|
|
1345
|
+
const pct = data.progress.messagesQueued > 0
|
|
1346
|
+
? Math.round((data.progress.messagesExported / data.progress.messagesQueued) * 100)
|
|
1347
|
+
: 0;
|
|
1348
|
+
progressBar.style.width = pct + '%';
|
|
1349
|
+
progressBar.setAttribute('aria-valuenow', pct);
|
|
1350
|
+
progressText.textContent =
|
|
1351
|
+
`${data.progress.messagesExported} / ${data.progress.messagesQueued} messages exported`;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Handle completion
|
|
1355
|
+
if (data.status === 'completed') {
|
|
1356
|
+
stopPolling();
|
|
1357
|
+
document.getElementById('export-download-section').classList.remove('d-none');
|
|
1358
|
+
document.getElementById('export-download-link').href =
|
|
1359
|
+
`/admin/accounts/${account}/export/${currentExportId}/download`;
|
|
1360
|
+
progressBar.style.width = '100%';
|
|
1361
|
+
progressBar.setAttribute('aria-valuenow', 100);
|
|
1362
|
+
progressBar.classList.remove('bg-info', 'progress-bar-striped', 'progress-bar-animated');
|
|
1363
|
+
progressBar.classList.add('bg-success');
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// Handle failure
|
|
1367
|
+
if (data.status === 'failed') {
|
|
1368
|
+
stopPolling();
|
|
1369
|
+
document.getElementById('export-error-section').classList.remove('d-none');
|
|
1370
|
+
document.getElementById('export-error-message').textContent = data.error || 'Export failed';
|
|
1371
|
+
document.getElementById('export-progress-section').classList.add('d-none');
|
|
1372
|
+
}
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
console.error('Failed to update export status', err);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
async function checkExistingExport() {
|
|
1379
|
+
const account = document.getElementById('account-id').value.trim();
|
|
1380
|
+
try {
|
|
1381
|
+
const res = await fetch(`/admin/accounts/${account}/exports?pageSize=1`);
|
|
1382
|
+
if (!res.ok) {
|
|
1383
|
+
showFormView();
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const data = await res.json();
|
|
1387
|
+
|
|
1388
|
+
if (data.exports && data.exports.length > 0) {
|
|
1389
|
+
const latest = data.exports[0];
|
|
1390
|
+
if (latest.status === 'queued' || latest.status === 'processing' || latest.status === 'completed') {
|
|
1391
|
+
currentExportId = latest.exportId;
|
|
1392
|
+
showStatusView();
|
|
1393
|
+
// Reset progress display state
|
|
1394
|
+
document.getElementById('export-progress-section').classList.remove('d-none');
|
|
1395
|
+
document.getElementById('export-error-section').classList.add('d-none');
|
|
1396
|
+
document.getElementById('export-download-section').classList.add('d-none');
|
|
1397
|
+
document.getElementById('export-phase-badge').classList.add('d-none');
|
|
1398
|
+
const progressBar = document.getElementById('export-progress-bar');
|
|
1399
|
+
progressBar.classList.remove('bg-success');
|
|
1400
|
+
progressBar.classList.add('bg-info', 'progress-bar-striped', 'progress-bar-animated');
|
|
1401
|
+
await updateExportStatus();
|
|
1402
|
+
if (latest.status !== 'completed' && latest.status !== 'failed') {
|
|
1403
|
+
startPolling();
|
|
1404
|
+
}
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
showFormView();
|
|
1409
|
+
} catch (err) {
|
|
1410
|
+
console.error('Failed to check existing export', err);
|
|
1411
|
+
showFormView();
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
$('#exportModal').on('show.bs.modal', async function () {
|
|
1416
|
+
await checkExistingExport();
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
$('#exportModal').on('hidden.bs.modal', function () {
|
|
1420
|
+
stopPolling();
|
|
1421
|
+
updateExportIndicator();
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
document.getElementById('export-start-btn').addEventListener('click', async function () {
|
|
1425
|
+
const account = document.getElementById('account-id').value.trim();
|
|
1426
|
+
const startDate = document.getElementById('export-start-date').value;
|
|
1427
|
+
const endDate = document.getElementById('export-end-date').value;
|
|
1428
|
+
const includeAttachments = document.getElementById('export-include-attachments').checked;
|
|
1429
|
+
|
|
1430
|
+
if (!startDate || !endDate) {
|
|
1431
|
+
showToast('Please select date range', 'alert-triangle');
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
document.getElementById('export-start-icon').classList.add('fa-spin');
|
|
1436
|
+
|
|
1437
|
+
try {
|
|
1438
|
+
const res = await fetch(`/admin/accounts/${account}/export`, {
|
|
1439
|
+
method: 'POST',
|
|
1440
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1441
|
+
body: JSON.stringify({
|
|
1442
|
+
crumb: document.getElementById('crumb').value,
|
|
1443
|
+
startDate: startDate + 'T00:00:00Z',
|
|
1444
|
+
endDate: endDate + 'T23:59:59Z',
|
|
1445
|
+
includeAttachments
|
|
1446
|
+
})
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
const data = await res.json();
|
|
1450
|
+
if (data.exportId) {
|
|
1451
|
+
currentExportId = data.exportId;
|
|
1452
|
+
showStatusView();
|
|
1453
|
+
// Reset progress display state
|
|
1454
|
+
document.getElementById('export-progress-section').classList.remove('d-none');
|
|
1455
|
+
document.getElementById('export-error-section').classList.add('d-none');
|
|
1456
|
+
document.getElementById('export-download-section').classList.add('d-none');
|
|
1457
|
+
document.getElementById('export-phase-badge').classList.add('d-none');
|
|
1458
|
+
const progressBar = document.getElementById('export-progress-bar');
|
|
1459
|
+
progressBar.classList.remove('bg-success');
|
|
1460
|
+
progressBar.classList.add('bg-info', 'progress-bar-striped', 'progress-bar-animated');
|
|
1461
|
+
progressBar.style.width = '0%';
|
|
1462
|
+
progressBar.setAttribute('aria-valuenow', 0);
|
|
1463
|
+
document.getElementById('export-progress-text').textContent = '';
|
|
1464
|
+
await updateExportStatus();
|
|
1465
|
+
startPolling();
|
|
1466
|
+
showToast('Export started', 'check-circle');
|
|
1467
|
+
} else {
|
|
1468
|
+
showToast(data.error || data.message || 'Failed to start export', 'alert-triangle');
|
|
1469
|
+
}
|
|
1470
|
+
} catch (err) {
|
|
1471
|
+
showToast('Export failed: ' + err.message, 'alert-triangle');
|
|
1472
|
+
} finally {
|
|
1473
|
+
document.getElementById('export-start-icon').classList.remove('fa-spin');
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
document.getElementById('export-reset-btn').addEventListener('click', async function () {
|
|
1478
|
+
if (!currentExportId) return;
|
|
1479
|
+
|
|
1480
|
+
const account = document.getElementById('account-id').value.trim();
|
|
1481
|
+
|
|
1482
|
+
try {
|
|
1483
|
+
await fetch(`/admin/accounts/${account}/export/${currentExportId}`, {
|
|
1484
|
+
method: 'DELETE',
|
|
1485
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1486
|
+
body: JSON.stringify({ crumb: document.getElementById('crumb').value })
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
stopPolling();
|
|
1490
|
+
currentExportId = null;
|
|
1491
|
+
showFormView();
|
|
1492
|
+
showToast('Export cleared', 'check-circle');
|
|
1493
|
+
} catch (err) {
|
|
1494
|
+
showToast('Failed to clear export: ' + err.message, 'alert-triangle');
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1124
1497
|
});
|
|
1125
1498
|
</script>
|
|
1126
1499
|
|
package/views/config/service.hbs
CHANGED
|
@@ -341,6 +341,41 @@
|
|
|
341
341
|
</div>
|
|
342
342
|
</div>
|
|
343
343
|
|
|
344
|
+
<div id="export_settings" class="card mb-4">
|
|
345
|
+
<div class="card-header py-3">
|
|
346
|
+
<h6 class="m-0 font-weight-bold text-primary">Export Settings</h6>
|
|
347
|
+
</div>
|
|
348
|
+
<div class="card-body">
|
|
349
|
+
<p>Configure batch sizes for bulk message export operations.</p>
|
|
350
|
+
|
|
351
|
+
<div class="form-group">
|
|
352
|
+
<div class="text-muted float-right code-link">[<a href="/admin/swagger#/Settings/postV1Settings"
|
|
353
|
+
target="_blank" rel="noopener noreferrer">gmailExportBatchSize</a>]</div>
|
|
354
|
+
<label for="settingsGmailExportBatchSize">Gmail Export Batch Size</label>
|
|
355
|
+
<input type="number" min="1" max="50" class="form-control {{#if errors.gmailExportBatchSize}}is-invalid{{/if}}"
|
|
356
|
+
id="settingsGmailExportBatchSize" name="gmailExportBatchSize" value="{{values.gmailExportBatchSize}}"
|
|
357
|
+
data-lpignore="true" autocomplete="off" />
|
|
358
|
+
{{#if errors.gmailExportBatchSize}}
|
|
359
|
+
<span class="invalid-feedback">{{errors.gmailExportBatchSize}}</span>
|
|
360
|
+
{{/if}}
|
|
361
|
+
<small class="form-text text-muted">Number of parallel requests when fetching Gmail messages for export (1-50, default: 10).</small>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div class="form-group">
|
|
365
|
+
<div class="text-muted float-right code-link">[<a href="/admin/swagger#/Settings/postV1Settings"
|
|
366
|
+
target="_blank" rel="noopener noreferrer">outlookExportBatchSize</a>]</div>
|
|
367
|
+
<label for="settingsOutlookExportBatchSize">Outlook Export Batch Size</label>
|
|
368
|
+
<input type="number" min="1" max="20" class="form-control {{#if errors.outlookExportBatchSize}}is-invalid{{/if}}"
|
|
369
|
+
id="settingsOutlookExportBatchSize" name="outlookExportBatchSize" value="{{values.outlookExportBatchSize}}"
|
|
370
|
+
data-lpignore="true" autocomplete="off" />
|
|
371
|
+
{{#if errors.outlookExportBatchSize}}
|
|
372
|
+
<span class="invalid-feedback">{{errors.outlookExportBatchSize}}</span>
|
|
373
|
+
{{/if}}
|
|
374
|
+
<small class="form-text text-muted">Messages per batch request for Outlook exports (1-20, default: 20). Limited by Microsoft Graph API.</small>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
|
|
344
379
|
<div id="templates_settings" class="card mb-4">
|
|
345
380
|
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
|
|
346
381
|
<h6 class="m-0 font-weight-bold text-primary">Public Page Customization</h6>
|