@saltcorn/server 0.8.7-beta.3 → 0.8.7-beta.4
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/auth/admin.js +3 -3
- package/locales/en.json +17 -1
- package/locales/ru.json +49 -8
- package/markup/admin.js +1 -1
- package/markup/forms.js +8 -2
- package/package.json +8 -8
- package/public/saltcorn-common.js +11 -0
- package/public/saltcorn.js +18 -2
- package/routes/actions.js +6 -6
- package/routes/admin.js +144 -41
- package/routes/fields.js +3 -3
- package/routes/files.js +21 -21
- package/routes/notifications.js +1 -1
- package/routes/plugins.js +4 -3
- package/routes/tables.js +1 -1
- package/tests/clientjs.test.js +3 -0
- package/tests/files.test.js +123 -2
package/auth/admin.js
CHANGED
|
@@ -55,7 +55,7 @@ module.exports = router;
|
|
|
55
55
|
*/
|
|
56
56
|
const getUserFields = async (req) => {
|
|
57
57
|
const userTable = Table.findOne({ name: "users" });
|
|
58
|
-
const userFields = (
|
|
58
|
+
const userFields = (userTable.getFields()).filter(
|
|
59
59
|
(f) => !f.calculated && f.name !== "id"
|
|
60
60
|
);
|
|
61
61
|
//console.log("userFields:",userFields);
|
|
@@ -265,7 +265,7 @@ router.get(
|
|
|
265
265
|
class: "form-control",
|
|
266
266
|
type: "search",
|
|
267
267
|
"data-filter-table": "table.user-admin",
|
|
268
|
-
placeholder: "
|
|
268
|
+
placeholder: `🔍 ${req.__("Search")}`,
|
|
269
269
|
})
|
|
270
270
|
)
|
|
271
271
|
),
|
|
@@ -280,7 +280,7 @@ router.get(
|
|
|
280
280
|
label: "",
|
|
281
281
|
key: (r) =>
|
|
282
282
|
r.disabled
|
|
283
|
-
? span({ class: "badge bg-danger" }, "Disabled")
|
|
283
|
+
? span({ class: "badge bg-danger" }, req.__("Disabled"))
|
|
284
284
|
: "",
|
|
285
285
|
},
|
|
286
286
|
{
|
package/locales/en.json
CHANGED
|
@@ -1175,5 +1175,21 @@
|
|
|
1175
1175
|
"App version": "App version",
|
|
1176
1176
|
"Forgot password?": "Forgot password?",
|
|
1177
1177
|
"Details": "Details",
|
|
1178
|
-
"URL is a formula?": "URL is a formula?"
|
|
1178
|
+
"URL is a formula?": "URL is a formula?",
|
|
1179
|
+
"Receive notifications by:": "Receive notifications by:",
|
|
1180
|
+
"Backup settings": "Backup settings",
|
|
1181
|
+
"Logo image": "Logo image",
|
|
1182
|
+
"Custom code": "Custom code",
|
|
1183
|
+
"Extension store": "Extension store",
|
|
1184
|
+
"Backup file prefix": "Backup file prefix",
|
|
1185
|
+
"Directory for backup files": "Directory for backup files",
|
|
1186
|
+
"Backup File Prefix": "Backup File Prefix",
|
|
1187
|
+
"Search for...": "Search for...",
|
|
1188
|
+
"Every 5 minutes": "Every 5 minutes",
|
|
1189
|
+
"Not scheduled but can be run as an action from a button click": "Not scheduled but can be run as an action from a button click",
|
|
1190
|
+
"Fixed and blocked fields": "Fixed and blocked fields",
|
|
1191
|
+
"Do not allow the following fields to have a value set from the query string or state": "Do not allow the following fields to have a value set from the query string or state",
|
|
1192
|
+
"Action configuration saved": "Action configuration saved",
|
|
1193
|
+
"Prevent any deletion of parent rows": "Prevent any deletion of parent rows",
|
|
1194
|
+
"If the parent row is deleted, set key fields on child rows to null": "If the parent row is deleted, set key fields on child rows to null"
|
|
1179
1195
|
}
|
package/locales/ru.json
CHANGED
|
@@ -164,8 +164,8 @@
|
|
|
164
164
|
"Installed": "Установлены",
|
|
165
165
|
"Refresh": "Обновить",
|
|
166
166
|
"Upgrade installed plugins": "Обновить установленные плагины",
|
|
167
|
-
"Add another plugin": "
|
|
168
|
-
"Add another pack": "
|
|
167
|
+
"Add another plugin": "Установить плагин",
|
|
168
|
+
"Add another pack": "Установить пакет",
|
|
169
169
|
"Create pack": "Создать пакет",
|
|
170
170
|
"Pack": "Пакет",
|
|
171
171
|
"Install": "Установить",
|
|
@@ -261,8 +261,8 @@
|
|
|
261
261
|
"New field:": "Новое поле:",
|
|
262
262
|
"Field attributes": "Атрибуты поля",
|
|
263
263
|
"Field %s created": "Поле %s создано",
|
|
264
|
-
"Summary field": "Поле
|
|
265
|
-
"A default value is required when adding required fields to nonempty tables": "
|
|
264
|
+
"Summary field": "Поле сводки",
|
|
265
|
+
"A default value is required when adding required fields to nonempty tables": "Значение по умолчанию требуется при добавлении обязательных полей в непустные таблицы",
|
|
266
266
|
"Create user": "Создать пользователя",
|
|
267
267
|
"Please create your first user account, which will have administrative privileges. You can add other users and give them administrative privileges later.": "Пожалуйства создайте первого пользователя, который будет обладать правами администратора. Позже вы можете создать остальных пользователей и наделить их правами администратора при необходимости.",
|
|
268
268
|
"Create first user": "Создать первого пользователя",
|
|
@@ -807,9 +807,9 @@
|
|
|
807
807
|
"Automated backup": "Автоматическое резервное копирование",
|
|
808
808
|
"Snapshots": "Снимки (снепшоты)",
|
|
809
809
|
"Mobile app": "Мобильное приложение",
|
|
810
|
-
"Module store": "Магазин
|
|
810
|
+
"Module store": "Магазин расширений",
|
|
811
811
|
"Upgrade installed modules": "Обновить установленные модули",
|
|
812
|
-
"Add another module": "
|
|
812
|
+
"Add another module": "Установить модуль",
|
|
813
813
|
"Module": "Модуль",
|
|
814
814
|
"Module installation and control": "Установка и управление модулями",
|
|
815
815
|
"System logging verbosity": "Уровень логирования системы",
|
|
@@ -1004,10 +1004,51 @@
|
|
|
1004
1004
|
"Connected views": "Связанные представления",
|
|
1005
1005
|
"Embedded in": "Встроенно в",
|
|
1006
1006
|
"Linked from": "Ссылается из",
|
|
1007
|
-
"First user E-mail": "
|
|
1007
|
+
"First user E-mail": "E-mail первого пользователя",
|
|
1008
1008
|
"Table constraints": "Констрейнты таблиц",
|
|
1009
1009
|
"Sessions": "Сессии",
|
|
1010
1010
|
"Event logs": "Лог событий",
|
|
1011
1011
|
"Migrations": "Миграции",
|
|
1012
|
-
"Tag Entries": "Экземпляры тегов"
|
|
1012
|
+
"Tag Entries": "Экземпляры тегов",
|
|
1013
|
+
"Locale identifier short code, e.g. en, zh, fr, ar etc. ": "Короткий код локали, например: en, zh, fr, ar и т.д. ",
|
|
1014
|
+
" with password %s": " с паролем %s",
|
|
1015
|
+
"Import table %s": "Импорт таблицы %s",
|
|
1016
|
+
"Import CSV": "Импорт CSV",
|
|
1017
|
+
"Open": "Открыть",
|
|
1018
|
+
"App name": "Название приложения",
|
|
1019
|
+
"App version": "Версия приложения",
|
|
1020
|
+
"App icon": "Иконка приложения",
|
|
1021
|
+
"Splash Page": "Заставка",
|
|
1022
|
+
"Allow offline mode": "Разрешить режим оффлайн",
|
|
1023
|
+
"URL is a formula?": "Является ли URL формулой?",
|
|
1024
|
+
"In scope:": "В области (In scope):",
|
|
1025
|
+
"Destination page": "Целевая страница",
|
|
1026
|
+
"Include in full-text search": "Включить в полнотекстовый поиск",
|
|
1027
|
+
"The field that will be shown to the user when choosing a value": "Данное поле будет показано пользователю при выборе значения из связанной таблицы",
|
|
1028
|
+
"On delete": "При удалении",
|
|
1029
|
+
"If the parent row is deleted, do this to the child rows.": "В случае удаления родительской записи, выполнить указанное действие для дочерних записей.",
|
|
1030
|
+
"Set a default value for missing data": "Установить значение для недостающих данных",
|
|
1031
|
+
"Prevent any deletion of parent rows": "Запретить удаление родительских строк",
|
|
1032
|
+
"If the parent row is deleted, automatically delete the child rows.": "При удалении родительской записи автоматически удалять дочерние записи.",
|
|
1033
|
+
"If the parent row is deleted, set key fields on child rows to null": "При удалении родительской записи установить ключевые поля в null для дочерних записей",
|
|
1034
|
+
"Every 5 minutes": "Каждые 5 минут",
|
|
1035
|
+
"Not scheduled but can be run as an action from a button click": "Не запланировано, но может быть запущено по кнопке",
|
|
1036
|
+
"Action configuration saved": "Конфигурация действия сохранена",
|
|
1037
|
+
"Action information saved": "Информация о действии сохранена",
|
|
1038
|
+
"JavaScript code:": "JavaScript code:",
|
|
1039
|
+
"code here": "ваш программный код",
|
|
1040
|
+
"No notifications": "Нет уведомлений",
|
|
1041
|
+
"Receive notifications by:": "Receive notifications by:",
|
|
1042
|
+
"Backup file prefix": "Префикс имени файлов backup",
|
|
1043
|
+
"Directory for backup files": "Папка для файлов backup",
|
|
1044
|
+
"Backup File Prefix": "Префикс имени файлов backup",
|
|
1045
|
+
"Module %s installed": "Модуль %s установлен",
|
|
1046
|
+
"View patterns": "Паттерны представления",
|
|
1047
|
+
"%s module information": "Информация о модуле %s",
|
|
1048
|
+
"Search for...": "Поиск...",
|
|
1049
|
+
"Modules up-to-date. Please restart server": "Модули актуальны. Пожалуйста рестартуйте сервер",
|
|
1050
|
+
"Logo image": "Логотип сайта",
|
|
1051
|
+
"Custom code": "Пользовательский код",
|
|
1052
|
+
"Extension store": "Магазин расширений",
|
|
1053
|
+
"Progressive Web Application": "Прогрессивное веб-приложение (PWA)"
|
|
1013
1054
|
}
|
package/markup/admin.js
CHANGED
package/markup/forms.js
CHANGED
|
@@ -63,10 +63,16 @@ const fileUploadForm = (req, folder) => {
|
|
|
63
63
|
name: "file",
|
|
64
64
|
class: "form-control ms-1 w-unset d-inline",
|
|
65
65
|
type: "file",
|
|
66
|
-
onchange: "form
|
|
66
|
+
onchange: "handle_upload_file_change(form)",
|
|
67
67
|
multiple: true,
|
|
68
68
|
}),
|
|
69
|
-
folder &&
|
|
69
|
+
folder &&
|
|
70
|
+
input({
|
|
71
|
+
id: "uploadFolderInpId",
|
|
72
|
+
type: "hidden",
|
|
73
|
+
name: "folder",
|
|
74
|
+
value: folder,
|
|
75
|
+
})
|
|
70
76
|
);
|
|
71
77
|
return frm;
|
|
72
78
|
};
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.7-beta.
|
|
3
|
+
"version": "0.8.7-beta.4",
|
|
4
4
|
"description": "Server app for Saltcorn, open-source no-code platform",
|
|
5
5
|
"homepage": "https://saltcorn.com",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@saltcorn/base-plugin": "0.8.7-beta.
|
|
10
|
-
"@saltcorn/builder": "0.8.7-beta.
|
|
11
|
-
"@saltcorn/data": "0.8.7-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.7-beta.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.7-beta.
|
|
14
|
-
"@saltcorn/markup": "0.8.7-beta.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.7-beta.
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.7-beta.4",
|
|
10
|
+
"@saltcorn/builder": "0.8.7-beta.4",
|
|
11
|
+
"@saltcorn/data": "0.8.7-beta.4",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.7-beta.4",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.7-beta.4",
|
|
14
|
+
"@saltcorn/markup": "0.8.7-beta.4",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.7-beta.4",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"adm-zip": "0.5.10",
|
|
@@ -143,6 +143,13 @@ function apply_showif() {
|
|
|
143
143
|
} value="${value}">${label}</option>`;
|
|
144
144
|
e.append($(html));
|
|
145
145
|
});
|
|
146
|
+
//TODO: also sort inserted HTML options
|
|
147
|
+
dataOptions.sort((a, b) =>
|
|
148
|
+
(a.text?.toLowerCase?.() || a.text) >
|
|
149
|
+
(b.text?.toLowerCase?.() || b.text)
|
|
150
|
+
? 1
|
|
151
|
+
: -1
|
|
152
|
+
);
|
|
146
153
|
element.dispatchEvent(new Event("RefreshSelectOptions"));
|
|
147
154
|
if (e.hasClass("selectized") && $().selectize) {
|
|
148
155
|
e.selectize()[0].selectize.clearOptions(true);
|
|
@@ -841,6 +848,10 @@ function notifyAlert(note, spin) {
|
|
|
841
848
|
</div>`);
|
|
842
849
|
}
|
|
843
850
|
|
|
851
|
+
function emptyAlerts() {
|
|
852
|
+
$("#alerts-area").html("");
|
|
853
|
+
}
|
|
854
|
+
|
|
844
855
|
function press_store_button(clicked) {
|
|
845
856
|
const width = $(clicked).width();
|
|
846
857
|
$(clicked).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
|
package/public/saltcorn.js
CHANGED
|
@@ -51,13 +51,13 @@ function removeQueryStringParameter(uri1, key) {
|
|
|
51
51
|
uri = uris[0];
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "
|
|
55
|
-
var separator = uri.indexOf("?") !== -1 ? "&" : "?";
|
|
54
|
+
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "gi");
|
|
56
55
|
if (uri.match(re)) {
|
|
57
56
|
uri = uri.replace(re, "$1" + "$2");
|
|
58
57
|
}
|
|
59
58
|
if (uri[uri.length - 1] === "?" || uri[uri.length - 1] === "&")
|
|
60
59
|
uri = uri.substring(0, uri.length - 1);
|
|
60
|
+
if (uri.match(re)) return removeQueryStringParameter(uri + hash, key);
|
|
61
61
|
return uri + hash;
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -561,6 +561,22 @@ function create_new_folder(folder) {
|
|
|
561
561
|
});
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
function handle_upload_file_change(form) {
|
|
565
|
+
const url = new URL(window.location);
|
|
566
|
+
const dir = url.searchParams.get("dir");
|
|
567
|
+
if (dir !== null) $("#uploadFolderInpId").val(dir);
|
|
568
|
+
const jqForm = $(form);
|
|
569
|
+
const sortBy = url.searchParams.get("sortBy");
|
|
570
|
+
if (sortBy) {
|
|
571
|
+
jqForm.append(`<input type="hidden" name="sortBy" value="${sortBy}" />`);
|
|
572
|
+
}
|
|
573
|
+
const sortDesc = url.searchParams.get("sortDesc");
|
|
574
|
+
if (sortDesc === "on") {
|
|
575
|
+
jqForm.append('<input type="hidden" name="sortDesc" value="on" />');
|
|
576
|
+
}
|
|
577
|
+
form.submit();
|
|
578
|
+
}
|
|
579
|
+
|
|
564
580
|
async function fill_formula_btn_click(btn, k) {
|
|
565
581
|
const formula = decodeURIComponent($(btn).attr("data-formula"));
|
|
566
582
|
const free_vars = JSON.parse(
|
package/routes/actions.js
CHANGED
|
@@ -173,9 +173,9 @@ const triggerForm = async (req, trigger) => {
|
|
|
173
173
|
sublabel: req.__("Event type which runs the trigger"),
|
|
174
174
|
attributes: {
|
|
175
175
|
explainers: {
|
|
176
|
-
Often: "Every 5 minutes",
|
|
176
|
+
Often: req.__("Every 5 minutes"),
|
|
177
177
|
Never:
|
|
178
|
-
"Not scheduled but can be run as an action from a button click",
|
|
178
|
+
req.__("Not scheduled but can be run as an action from a button click"),
|
|
179
179
|
},
|
|
180
180
|
},
|
|
181
181
|
},
|
|
@@ -358,7 +358,7 @@ router.post(
|
|
|
358
358
|
});
|
|
359
359
|
} else {
|
|
360
360
|
await Trigger.update(trigger.id, form.values); //{configuration: form.values});
|
|
361
|
-
req.flash("success", "Action information saved");
|
|
361
|
+
req.flash("success", req.__("Action information saved"));
|
|
362
362
|
res.redirect(`/actions/`);
|
|
363
363
|
}
|
|
364
364
|
})
|
|
@@ -443,13 +443,13 @@ router.get(
|
|
|
443
443
|
)
|
|
444
444
|
)
|
|
445
445
|
),
|
|
446
|
-
h6({ class: "mt-1" }, "JavaScript code:"),
|
|
446
|
+
h6({ class: "mt-1" }, req.__("JavaScript code:")),
|
|
447
447
|
div(
|
|
448
448
|
{ class: "mt-1" },
|
|
449
449
|
|
|
450
450
|
pre(
|
|
451
451
|
{ class: "js-code-display" },
|
|
452
|
-
code({ id: "blockly_js_output" }, "code here")
|
|
452
|
+
code({ id: "blockly_js_output" }, req.__("code here"))
|
|
453
453
|
)
|
|
454
454
|
),
|
|
455
455
|
],
|
|
@@ -538,7 +538,7 @@ router.post(
|
|
|
538
538
|
res.json({ success: "ok" });
|
|
539
539
|
return;
|
|
540
540
|
}
|
|
541
|
-
req.flash("success", "Action configuration saved");
|
|
541
|
+
req.flash("success", req.__("Action configuration saved"));
|
|
542
542
|
res.redirect(
|
|
543
543
|
req.query.on_done_redirect &&
|
|
544
544
|
is_relative_url(req.query.on_done_redirect)
|
package/routes/admin.js
CHANGED
|
@@ -249,6 +249,11 @@ router.get(
|
|
|
249
249
|
"/backup",
|
|
250
250
|
isAdmin,
|
|
251
251
|
error_catcher(async (req, res) => {
|
|
252
|
+
//
|
|
253
|
+
const aBackupFilePrefixForm = backupFilePrefixForm(req);
|
|
254
|
+
aBackupFilePrefixForm.values.backup_file_prefix =
|
|
255
|
+
getState().getConfig("backup_file_prefix");
|
|
256
|
+
//
|
|
252
257
|
const backupForm = autoBackupForm(req);
|
|
253
258
|
backupForm.values.auto_backup_frequency = getState().getConfig(
|
|
254
259
|
"auto_backup_frequency"
|
|
@@ -262,11 +267,13 @@ router.get(
|
|
|
262
267
|
backupForm.values.auto_backup_expire_days = getState().getConfig(
|
|
263
268
|
"auto_backup_expire_days"
|
|
264
269
|
);
|
|
270
|
+
//
|
|
265
271
|
const aSnapshotForm = snapshotForm(req);
|
|
266
272
|
aSnapshotForm.values.snapshots_enabled =
|
|
267
273
|
getState().getConfig("snapshots_enabled");
|
|
274
|
+
//
|
|
268
275
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
269
|
-
|
|
276
|
+
//
|
|
270
277
|
send_admin_page({
|
|
271
278
|
res,
|
|
272
279
|
req,
|
|
@@ -367,6 +374,12 @@ router.get(
|
|
|
367
374
|
)
|
|
368
375
|
),
|
|
369
376
|
},
|
|
377
|
+
{
|
|
378
|
+
type: "card",
|
|
379
|
+
title: req.__("Backup settings"),
|
|
380
|
+
titleAjaxIndicator: true,
|
|
381
|
+
contents: div(renderForm(aBackupFilePrefixForm, req.csrfToken())),
|
|
382
|
+
},
|
|
370
383
|
],
|
|
371
384
|
},
|
|
372
385
|
});
|
|
@@ -388,10 +401,17 @@ router.get(
|
|
|
388
401
|
return;
|
|
389
402
|
}
|
|
390
403
|
const auto_backup_directory = getState().getConfig("auto_backup_directory");
|
|
391
|
-
|
|
404
|
+
|
|
405
|
+
const backup_file_prefix = getState().getConfig("backup_file_prefix");
|
|
406
|
+
|
|
407
|
+
const fileNms = auto_backup_directory
|
|
408
|
+
? await fs.promises.readdir(auto_backup_directory)
|
|
409
|
+
: [];
|
|
410
|
+
|
|
392
411
|
const backupFiles = fileNms.filter(
|
|
393
|
-
(fnm) => fnm.startsWith(
|
|
412
|
+
(fnm) => fnm.startsWith(backup_file_prefix) && fnm.endsWith(".zip")
|
|
394
413
|
);
|
|
414
|
+
|
|
395
415
|
send_admin_page({
|
|
396
416
|
res,
|
|
397
417
|
req,
|
|
@@ -402,20 +422,22 @@ router.get(
|
|
|
402
422
|
type: "card",
|
|
403
423
|
title: req.__("Download automated backup"),
|
|
404
424
|
contents: div(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
425
|
+
backupFiles.length > 0
|
|
426
|
+
? ul(
|
|
427
|
+
backupFiles.map((fnm) =>
|
|
428
|
+
li(
|
|
429
|
+
a(
|
|
430
|
+
{
|
|
431
|
+
href: `/admin/auto-backup-download/${encodeURIComponent(
|
|
432
|
+
fnm
|
|
433
|
+
)}`,
|
|
434
|
+
},
|
|
411
435
|
fnm
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
fnm
|
|
436
|
+
)
|
|
437
|
+
)
|
|
415
438
|
)
|
|
416
439
|
)
|
|
417
|
-
)
|
|
418
|
-
)
|
|
440
|
+
: p(req.__("No files"))
|
|
419
441
|
),
|
|
420
442
|
},
|
|
421
443
|
{
|
|
@@ -465,23 +487,25 @@ router.get(
|
|
|
465
487
|
type: "card",
|
|
466
488
|
title: req.__("Download snapshots"),
|
|
467
489
|
contents: div(
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
490
|
+
snaps.length > 0
|
|
491
|
+
? ul(
|
|
492
|
+
snaps.map((snap) =>
|
|
493
|
+
li(
|
|
494
|
+
a(
|
|
495
|
+
{
|
|
496
|
+
href: `/admin/snapshot-download/${encodeURIComponent(
|
|
497
|
+
snap.id
|
|
498
|
+
)}`,
|
|
499
|
+
target: "_blank",
|
|
500
|
+
},
|
|
501
|
+
`${localeDateTime(snap.created)} (${moment(
|
|
502
|
+
snap.created
|
|
503
|
+
).fromNow()})`
|
|
504
|
+
)
|
|
505
|
+
)
|
|
481
506
|
)
|
|
482
507
|
)
|
|
483
|
-
)
|
|
484
|
-
)
|
|
508
|
+
: p(req.__("No files"))
|
|
485
509
|
),
|
|
486
510
|
},
|
|
487
511
|
],
|
|
@@ -588,9 +612,10 @@ router.get(
|
|
|
588
612
|
error_catcher(async (req, res) => {
|
|
589
613
|
const { filename } = req.params;
|
|
590
614
|
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
615
|
+
const backup_file_prefix = getState().getConfig("backup_file_prefix");
|
|
591
616
|
if (
|
|
592
617
|
!isRoot ||
|
|
593
|
-
!(filename.startsWith(
|
|
618
|
+
!(filename.startsWith(backup_file_prefix) && filename.endsWith(".zip"))
|
|
594
619
|
) {
|
|
595
620
|
res.redirect("/admin/backup");
|
|
596
621
|
return;
|
|
@@ -600,6 +625,27 @@ router.get(
|
|
|
600
625
|
})
|
|
601
626
|
);
|
|
602
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Set Backup File Prefix Form
|
|
630
|
+
* @param req
|
|
631
|
+
* @returns {Form}
|
|
632
|
+
*/
|
|
633
|
+
const backupFilePrefixForm = (req) =>
|
|
634
|
+
new Form({
|
|
635
|
+
action: "/admin/set-backup-prefix",
|
|
636
|
+
onChange: `saveAndContinue(this);`,
|
|
637
|
+
noSubmitButton: true,
|
|
638
|
+
fields: [
|
|
639
|
+
{
|
|
640
|
+
type: "String",
|
|
641
|
+
label: req.__("Backup file prefix"),
|
|
642
|
+
name: "backup_file_prefix",
|
|
643
|
+
sublabel: req.__("Backup file prefix"),
|
|
644
|
+
default: "sc-backup-",
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
});
|
|
648
|
+
|
|
603
649
|
/**
|
|
604
650
|
* Auto backup Form
|
|
605
651
|
* @param {object} req
|
|
@@ -638,9 +684,10 @@ const autoBackupForm = (req) =>
|
|
|
638
684
|
type: "String",
|
|
639
685
|
label: req.__("Directory"),
|
|
640
686
|
name: "auto_backup_directory",
|
|
687
|
+
sublabel: req.__("Directory for backup files"),
|
|
641
688
|
showIf: {
|
|
642
689
|
auto_backup_frequency: ["Daily", "Weekly"],
|
|
643
|
-
auto_backup_destination: "Local directory",
|
|
690
|
+
//auto_backup_destination: "Local directory",
|
|
644
691
|
},
|
|
645
692
|
},
|
|
646
693
|
{
|
|
@@ -658,6 +705,11 @@ const autoBackupForm = (req) =>
|
|
|
658
705
|
],
|
|
659
706
|
});
|
|
660
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Snapshot Form
|
|
710
|
+
* @param req
|
|
711
|
+
* @returns {Form}
|
|
712
|
+
*/
|
|
661
713
|
const snapshotForm = (req) =>
|
|
662
714
|
new Form({
|
|
663
715
|
action: "/admin/set-snapshot",
|
|
@@ -682,6 +734,9 @@ const snapshotForm = (req) =>
|
|
|
682
734
|
},
|
|
683
735
|
],
|
|
684
736
|
});
|
|
737
|
+
/**
|
|
738
|
+
* Do Set snapshot
|
|
739
|
+
*/
|
|
685
740
|
router.post(
|
|
686
741
|
"/set-snapshot",
|
|
687
742
|
isAdmin,
|
|
@@ -697,6 +752,38 @@ router.post(
|
|
|
697
752
|
} else res.json({ success: "ok" });
|
|
698
753
|
})
|
|
699
754
|
);
|
|
755
|
+
/**
|
|
756
|
+
* Do Set Backup Prefix
|
|
757
|
+
*/
|
|
758
|
+
router.post(
|
|
759
|
+
"/set-backup-prefix",
|
|
760
|
+
isAdmin,
|
|
761
|
+
error_catcher(async (req, res) => {
|
|
762
|
+
const form = await backupFilePrefixForm(req);
|
|
763
|
+
form.validate(req.body);
|
|
764
|
+
if (form.hasErrors) {
|
|
765
|
+
send_admin_page({
|
|
766
|
+
res,
|
|
767
|
+
req,
|
|
768
|
+
active_sub: "Backup",
|
|
769
|
+
contents: {
|
|
770
|
+
type: "card",
|
|
771
|
+
title: req.__("Backup settings"),
|
|
772
|
+
contents: [renderForm(form, req.csrfToken())],
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
} else {
|
|
776
|
+
await save_config_from_form(form);
|
|
777
|
+
if (!req.xhr) {
|
|
778
|
+
req.flash("success", req.__("Backup settings updated"));
|
|
779
|
+
res.redirect("/admin/backup");
|
|
780
|
+
} else res.json({ success: "ok" });
|
|
781
|
+
}
|
|
782
|
+
})
|
|
783
|
+
);
|
|
784
|
+
/**
|
|
785
|
+
* Do Set auto backup
|
|
786
|
+
*/
|
|
700
787
|
router.post(
|
|
701
788
|
"/set-auto-backup",
|
|
702
789
|
isAdmin,
|
|
@@ -723,6 +810,9 @@ router.post(
|
|
|
723
810
|
}
|
|
724
811
|
})
|
|
725
812
|
);
|
|
813
|
+
/**
|
|
814
|
+
* Do Auto backup now
|
|
815
|
+
*/
|
|
726
816
|
router.post(
|
|
727
817
|
"/auto-backup-now",
|
|
728
818
|
isAdmin,
|
|
@@ -737,7 +827,9 @@ router.post(
|
|
|
737
827
|
res.json({ reload_page: true });
|
|
738
828
|
})
|
|
739
829
|
);
|
|
740
|
-
|
|
830
|
+
/**
|
|
831
|
+
* Do Snapshot now
|
|
832
|
+
*/
|
|
741
833
|
router.post(
|
|
742
834
|
"/snapshot-now",
|
|
743
835
|
isAdmin,
|
|
@@ -755,6 +847,7 @@ router.post(
|
|
|
755
847
|
);
|
|
756
848
|
|
|
757
849
|
/**
|
|
850
|
+
* Show System page
|
|
758
851
|
* @name get/system
|
|
759
852
|
* @function
|
|
760
853
|
* @memberof module:routes/admin~routes/adminRouter
|
|
@@ -948,6 +1041,7 @@ router.get(
|
|
|
948
1041
|
);
|
|
949
1042
|
|
|
950
1043
|
/**
|
|
1044
|
+
* Do Restart
|
|
951
1045
|
* @name post/restart
|
|
952
1046
|
* @function
|
|
953
1047
|
* @memberof module:routes/admin~routes/adminRouter
|
|
@@ -972,6 +1066,7 @@ router.post(
|
|
|
972
1066
|
);
|
|
973
1067
|
|
|
974
1068
|
/**
|
|
1069
|
+
* Do Upgrade
|
|
975
1070
|
* @name post/upgrade
|
|
976
1071
|
* @function
|
|
977
1072
|
* @memberof module:routes/admin~routes/adminRouter
|
|
@@ -1013,7 +1108,7 @@ router.post(
|
|
|
1013
1108
|
})
|
|
1014
1109
|
);
|
|
1015
1110
|
/**
|
|
1016
|
-
*
|
|
1111
|
+
* Do Check for Update
|
|
1017
1112
|
*/
|
|
1018
1113
|
router.post(
|
|
1019
1114
|
"/check-for-upgrade",
|
|
@@ -1025,6 +1120,7 @@ router.post(
|
|
|
1025
1120
|
})
|
|
1026
1121
|
);
|
|
1027
1122
|
/**
|
|
1123
|
+
* Do Manual Backup
|
|
1028
1124
|
* @name post/backup
|
|
1029
1125
|
* @function
|
|
1030
1126
|
* @memberof module:routes/admin~routes/adminRouter
|
|
@@ -1045,6 +1141,7 @@ router.post(
|
|
|
1045
1141
|
);
|
|
1046
1142
|
|
|
1047
1143
|
/**
|
|
1144
|
+
* Do Restore from Backup
|
|
1048
1145
|
* @name post/restore
|
|
1049
1146
|
* @function
|
|
1050
1147
|
* @memberof module:routes/admin~routes/adminRouter
|
|
@@ -1143,6 +1240,7 @@ const clearAllForm = (req) =>
|
|
|
1143
1240
|
});
|
|
1144
1241
|
|
|
1145
1242
|
/**
|
|
1243
|
+
* Do Enable letsencrypt
|
|
1146
1244
|
* @name post/enable-letsencrypt
|
|
1147
1245
|
* @function
|
|
1148
1246
|
* @memberof module:routes/admin~routes/adminRouter
|
|
@@ -1222,6 +1320,7 @@ router.post(
|
|
|
1222
1320
|
);
|
|
1223
1321
|
|
|
1224
1322
|
/**
|
|
1323
|
+
* Do Clear All
|
|
1225
1324
|
* @name get/clear-all
|
|
1226
1325
|
* @function
|
|
1227
1326
|
* @memberof module:routes/admin~routes/adminRouter
|
|
@@ -1250,7 +1349,7 @@ router.get(
|
|
|
1250
1349
|
})
|
|
1251
1350
|
);
|
|
1252
1351
|
/**
|
|
1253
|
-
*
|
|
1352
|
+
* Do Configuration Check
|
|
1254
1353
|
*/
|
|
1255
1354
|
router.get(
|
|
1256
1355
|
"/configuration-check",
|
|
@@ -1337,7 +1436,6 @@ router.get(
|
|
|
1337
1436
|
});
|
|
1338
1437
|
})
|
|
1339
1438
|
);
|
|
1340
|
-
|
|
1341
1439
|
const buildDialogScript = () => {
|
|
1342
1440
|
return `<script>
|
|
1343
1441
|
function swapEntryInputs(activeTab, activeInput, disabledTab, disabledInput) {
|
|
@@ -1760,7 +1858,9 @@ router.get(
|
|
|
1760
1858
|
});
|
|
1761
1859
|
})
|
|
1762
1860
|
);
|
|
1763
|
-
|
|
1861
|
+
/**
|
|
1862
|
+
* Do Build Mobile App
|
|
1863
|
+
*/
|
|
1764
1864
|
router.post(
|
|
1765
1865
|
"/build-mobile-app",
|
|
1766
1866
|
isAdmin,
|
|
@@ -1788,7 +1888,7 @@ router.post(
|
|
|
1788
1888
|
error: req.__("Only the android build supports docker."),
|
|
1789
1889
|
});
|
|
1790
1890
|
}
|
|
1791
|
-
if (!serverURL || serverURL.length
|
|
1891
|
+
if (!serverURL || serverURL.length === 0) {
|
|
1792
1892
|
serverURL = getState().getConfig("base_url") || "";
|
|
1793
1893
|
}
|
|
1794
1894
|
if (!serverURL.startsWith("http")) {
|
|
@@ -1888,8 +1988,7 @@ router.post(
|
|
|
1888
1988
|
);
|
|
1889
1989
|
|
|
1890
1990
|
/**
|
|
1891
|
-
* Clear
|
|
1892
|
-
* @name post/clear-all
|
|
1991
|
+
* Do Clear All
|
|
1893
1992
|
* @function
|
|
1894
1993
|
* @memberof module:routes/admin~routes/adminRouter
|
|
1895
1994
|
*/
|
|
@@ -2012,7 +2111,9 @@ router.post(
|
|
|
2012
2111
|
}
|
|
2013
2112
|
})
|
|
2014
2113
|
);
|
|
2015
|
-
|
|
2114
|
+
/**
|
|
2115
|
+
* Dev / Admin
|
|
2116
|
+
*/
|
|
2016
2117
|
admin_config_route({
|
|
2017
2118
|
router,
|
|
2018
2119
|
path: "/dev",
|
|
@@ -2051,7 +2152,9 @@ admin_config_route({
|
|
|
2051
2152
|
});
|
|
2052
2153
|
},
|
|
2053
2154
|
});
|
|
2054
|
-
|
|
2155
|
+
/**
|
|
2156
|
+
* Notifications
|
|
2157
|
+
*/
|
|
2055
2158
|
admin_config_route({
|
|
2056
2159
|
router,
|
|
2057
2160
|
path: "/notifications",
|
package/routes/fields.js
CHANGED
|
@@ -454,11 +454,11 @@ const fieldFlow = (req) =>
|
|
|
454
454
|
required: true,
|
|
455
455
|
attributes: {
|
|
456
456
|
explainers: {
|
|
457
|
-
Fail: "Prevent any deletion of parent rows",
|
|
457
|
+
Fail: req.__("Prevent any deletion of parent rows"),
|
|
458
458
|
Cascade:
|
|
459
|
-
|
|
459
|
+
req.__("If the parent row is deleted, automatically delete the child rows."),
|
|
460
460
|
"Set null":
|
|
461
|
-
|
|
461
|
+
req.__("If the parent row is deleted, set key fields on child rows to null"),
|
|
462
462
|
},
|
|
463
463
|
},
|
|
464
464
|
sublabel: req.__(
|
package/routes/files.js
CHANGED
|
@@ -69,9 +69,12 @@ router.get(
|
|
|
69
69
|
isAdmin,
|
|
70
70
|
error_catcher(async (req, res) => {
|
|
71
71
|
// todo limit select from file by 10 or 20
|
|
72
|
-
const { dir } = req.query;
|
|
72
|
+
const { dir, search } = req.query;
|
|
73
73
|
const safeDir = File.normalise(dir || "/");
|
|
74
|
-
const rows = await File.find(
|
|
74
|
+
const rows = await File.find(
|
|
75
|
+
{ folder: dir, search },
|
|
76
|
+
{ orderBy: "filename" }
|
|
77
|
+
);
|
|
75
78
|
const roles = await User.get_roles();
|
|
76
79
|
if (safeDir && safeDir !== "/" && safeDir !== ".") {
|
|
77
80
|
let dirname = path.dirname(safeDir);
|
|
@@ -383,7 +386,7 @@ router.post(
|
|
|
383
386
|
"/upload",
|
|
384
387
|
setTenant,
|
|
385
388
|
error_catcher(async (req, res) => {
|
|
386
|
-
let { folder } = req.body;
|
|
389
|
+
let { folder, sortBy, sortDesc } = req.body;
|
|
387
390
|
let jsonResp = {};
|
|
388
391
|
const min_role_upload = getState().getConfig("min_role_upload", 1);
|
|
389
392
|
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
@@ -404,16 +407,11 @@ router.post(
|
|
|
404
407
|
);
|
|
405
408
|
const many = Array.isArray(f);
|
|
406
409
|
file_for_redirect = many ? f[0] : f;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
many
|
|
413
|
-
? f.map((fl) => text(fl.filename)).join(", ")
|
|
414
|
-
: text(f.filename)
|
|
415
|
-
)
|
|
416
|
-
);
|
|
410
|
+
const successMsg = req.__(
|
|
411
|
+
`File %s uploaded`,
|
|
412
|
+
many ? f.map((fl) => text(fl.filename)).join(", ") : text(f.filename)
|
|
413
|
+
);
|
|
414
|
+
if (!req.xhr) req.flash("success", successMsg);
|
|
417
415
|
else
|
|
418
416
|
jsonResp = {
|
|
419
417
|
success: {
|
|
@@ -422,16 +420,18 @@ router.post(
|
|
|
422
420
|
url: many
|
|
423
421
|
? f.map((fl) => `/files/serve/${fl.path_to_serve}`)
|
|
424
422
|
: `/files/serve/${f.path_to_serve}`,
|
|
423
|
+
msg: successMsg,
|
|
425
424
|
},
|
|
426
425
|
};
|
|
427
426
|
}
|
|
428
|
-
if (!req.xhr)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
);
|
|
434
|
-
|
|
427
|
+
if (!req.xhr) {
|
|
428
|
+
const sp = new URLSearchParams();
|
|
429
|
+
if (file_for_redirect) sp.append("dir", file_for_redirect.current_folder);
|
|
430
|
+
if (sortBy) sp.append("sortBy", sortBy);
|
|
431
|
+
if (sortDesc) sp.append("sortDesc", sortDesc);
|
|
432
|
+
const query = sp.toString();
|
|
433
|
+
res.redirect(`/files${query ? `?${query}` : ""}`);
|
|
434
|
+
} else res.json(jsonResp);
|
|
435
435
|
})
|
|
436
436
|
);
|
|
437
437
|
|
|
@@ -449,7 +449,7 @@ router.post(
|
|
|
449
449
|
const { redirect } = req.query;
|
|
450
450
|
const f = await File.findOne(serve_path);
|
|
451
451
|
if (!f) {
|
|
452
|
-
req.flash("error", "File not found");
|
|
452
|
+
req.flash("error", req.__("File not found"));
|
|
453
453
|
res.redirect("/files");
|
|
454
454
|
return;
|
|
455
455
|
}
|
package/routes/notifications.js
CHANGED
package/routes/plugins.js
CHANGED
|
@@ -526,7 +526,8 @@ const plugin_store_html = (items, req) => {
|
|
|
526
526
|
contents: div(
|
|
527
527
|
{ class: "d-flex justify-content-between" },
|
|
528
528
|
storeNavPills(req),
|
|
529
|
-
div(search_bar("q", req.query.q || "",
|
|
529
|
+
div(search_bar("q", req.query.q || "",
|
|
530
|
+
{ placeHolder: req.__("Search for..."), stateField: "q" })),
|
|
530
531
|
div(store_actions_dropdown(req))
|
|
531
532
|
),
|
|
532
533
|
},
|
|
@@ -825,7 +826,7 @@ router.get(
|
|
|
825
826
|
pkgjson = require(path.join(mod.location, "package.json"));
|
|
826
827
|
|
|
827
828
|
if (!plugin_db) {
|
|
828
|
-
req.flash("warning", "Module not found");
|
|
829
|
+
req.flash("warning", req.__("Module not found"));
|
|
829
830
|
res.redirect("/plugins");
|
|
830
831
|
return;
|
|
831
832
|
}
|
|
@@ -1035,7 +1036,7 @@ router.post(
|
|
|
1035
1036
|
|
|
1036
1037
|
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
1037
1038
|
if (!plugin) {
|
|
1038
|
-
req.flash("warning", "Module not found");
|
|
1039
|
+
req.flash("warning", req.__("Module not found"));
|
|
1039
1040
|
res.redirect("/plugins");
|
|
1040
1041
|
return;
|
|
1041
1042
|
}
|
package/routes/tables.js
CHANGED
|
@@ -1135,7 +1135,7 @@ router.get(
|
|
|
1135
1135
|
error_catcher(async (req, res) => {
|
|
1136
1136
|
const { name } = req.params;
|
|
1137
1137
|
const table = Table.findOne({ name });
|
|
1138
|
-
const rows = await table.getRows({}, { orderBy: "id"
|
|
1138
|
+
const rows = await table.getRows({}, { orderBy: "id" });
|
|
1139
1139
|
res.setHeader("Content-Type", "text/csv");
|
|
1140
1140
|
res.setHeader("Content-Disposition", `attachment; filename="${name}.csv"`);
|
|
1141
1141
|
res.setHeader("Cache-Control", "no-cache");
|
package/tests/clientjs.test.js
CHANGED
|
@@ -34,6 +34,9 @@ test("updateQueryStringParameter", () => {
|
|
|
34
34
|
expect(removeQueryStringParameter("/foo?name=Bar&age=45", "age")).toBe(
|
|
35
35
|
"/foo?name=Bar"
|
|
36
36
|
);
|
|
37
|
+
expect(
|
|
38
|
+
removeQueryStringParameter("/foo?name=Baz&name=Foo&age=45", "name")
|
|
39
|
+
).not.toContain("name");
|
|
37
40
|
expect(
|
|
38
41
|
updateQueryStringParameter("/foo", "publisher.publisher->name", "AK")
|
|
39
42
|
).toBe("/foo?publisher.publisher->name=AK");
|
package/tests/files.test.js
CHANGED
|
@@ -7,9 +7,9 @@ const {
|
|
|
7
7
|
itShouldRedirectUnauthToLogin,
|
|
8
8
|
toInclude,
|
|
9
9
|
toSucceed,
|
|
10
|
-
toNotInclude,
|
|
11
10
|
resetToFixtures,
|
|
12
11
|
toSucceedWithImage,
|
|
12
|
+
respondJsonWith,
|
|
13
13
|
} = require("../auth/testhelp");
|
|
14
14
|
const db = require("@saltcorn/data/db");
|
|
15
15
|
const fs = require("fs").promises;
|
|
@@ -18,7 +18,17 @@ const File = require("@saltcorn/data/models/file");
|
|
|
18
18
|
const Field = require("@saltcorn/data/models/field");
|
|
19
19
|
const Table = require("@saltcorn/data/models/table");
|
|
20
20
|
const View = require("@saltcorn/data/models/view");
|
|
21
|
-
const {
|
|
21
|
+
const { existsSync } = require("fs");
|
|
22
|
+
|
|
23
|
+
const createTestFile = async (folder, name, mimetype, content) => {
|
|
24
|
+
if (
|
|
25
|
+
!existsSync(
|
|
26
|
+
path.join(db.connectObj.file_store, db.getTenantSchema(), folder, name)
|
|
27
|
+
)
|
|
28
|
+
) {
|
|
29
|
+
await File.from_contents(name, mimetype, content, 1, 100, folder);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
22
32
|
|
|
23
33
|
beforeAll(async () => {
|
|
24
34
|
await resetToFixtures();
|
|
@@ -47,6 +57,33 @@ beforeAll(async () => {
|
|
|
47
57
|
1,
|
|
48
58
|
100
|
|
49
59
|
);
|
|
60
|
+
|
|
61
|
+
await File.new_folder(path.join("_sc_test_subfolder_one", "subsubfolder"));
|
|
62
|
+
await createTestFile(
|
|
63
|
+
"_sc_test_subfolder_one",
|
|
64
|
+
"foo_image.png",
|
|
65
|
+
"image/png",
|
|
66
|
+
"imagecontent"
|
|
67
|
+
);
|
|
68
|
+
await createTestFile(
|
|
69
|
+
"_sc_test_subfolder_one",
|
|
70
|
+
"bar_image.png",
|
|
71
|
+
"image/png",
|
|
72
|
+
"imagecontent"
|
|
73
|
+
);
|
|
74
|
+
await createTestFile(
|
|
75
|
+
path.join("_sc_test_subfolder_one", "subsubfolder"),
|
|
76
|
+
"bar_image.png",
|
|
77
|
+
"image/png",
|
|
78
|
+
"imagecontent"
|
|
79
|
+
);
|
|
80
|
+
await File.new_folder("_sc_test_subfolder_two");
|
|
81
|
+
await createTestFile(
|
|
82
|
+
"_sc_test_subfolder_two",
|
|
83
|
+
"foo_image.png",
|
|
84
|
+
"image/png",
|
|
85
|
+
"imagecontent"
|
|
86
|
+
);
|
|
50
87
|
});
|
|
51
88
|
afterAll(db.close);
|
|
52
89
|
|
|
@@ -146,6 +183,90 @@ describe("files admin", () => {
|
|
|
146
183
|
|
|
147
184
|
.expect(toRedirect("/files?dir=."));
|
|
148
185
|
});
|
|
186
|
+
it("search files by name", async () => {
|
|
187
|
+
const app = await getApp({ disableCsrf: true });
|
|
188
|
+
const loginCookie = await getAdminLoginCookie();
|
|
189
|
+
const checkFiles = (files, expecteds) =>
|
|
190
|
+
files.length === expecteds.length &&
|
|
191
|
+
expecteds.every(({ filename, location }) =>
|
|
192
|
+
files.find(
|
|
193
|
+
(file) => file.filename === filename && file.location === location
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
const searchTestHelper = async (dir, search, expected) => {
|
|
197
|
+
await request(app)
|
|
198
|
+
.get("/files")
|
|
199
|
+
.query({ dir, search })
|
|
200
|
+
.set("X-Requested-With", "XMLHttpRequest")
|
|
201
|
+
.set("Cookie", loginCookie)
|
|
202
|
+
.expect(
|
|
203
|
+
respondJsonWith(200, (data) => checkFiles(data.files, expected))
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
await searchTestHelper("/", "foo", [
|
|
208
|
+
{
|
|
209
|
+
filename: "foo_image.png",
|
|
210
|
+
location: path.join("_sc_test_subfolder_one", "foo_image.png"),
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
filename: "foo_image.png",
|
|
214
|
+
location: path.join("_sc_test_subfolder_two", "foo_image.png"),
|
|
215
|
+
},
|
|
216
|
+
]);
|
|
217
|
+
await searchTestHelper("_sc_test_subfolder_two", "foo", [
|
|
218
|
+
{
|
|
219
|
+
filename: "foo_image.png",
|
|
220
|
+
location: path.join("_sc_test_subfolder_two", "foo_image.png"),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
filename: "..",
|
|
224
|
+
location: "",
|
|
225
|
+
},
|
|
226
|
+
]);
|
|
227
|
+
await searchTestHelper("/", "bar", [
|
|
228
|
+
{
|
|
229
|
+
filename: "bar_image.png",
|
|
230
|
+
location: path.join("_sc_test_subfolder_one", "bar_image.png"),
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
filename: "bar_image.png",
|
|
234
|
+
location: path.join(
|
|
235
|
+
"_sc_test_subfolder_one",
|
|
236
|
+
"subsubfolder",
|
|
237
|
+
"bar_image.png"
|
|
238
|
+
),
|
|
239
|
+
},
|
|
240
|
+
]);
|
|
241
|
+
await searchTestHelper(
|
|
242
|
+
path.join("_sc_test_subfolder_one", "subsubfolder"),
|
|
243
|
+
"foo",
|
|
244
|
+
[
|
|
245
|
+
{
|
|
246
|
+
filename: "..",
|
|
247
|
+
location: "_sc_test_subfolder_one",
|
|
248
|
+
},
|
|
249
|
+
]
|
|
250
|
+
);
|
|
251
|
+
await searchTestHelper(
|
|
252
|
+
path.join("_sc_test_subfolder_one", "subsubfolder"),
|
|
253
|
+
"bar",
|
|
254
|
+
[
|
|
255
|
+
{
|
|
256
|
+
filename: "..",
|
|
257
|
+
location: "_sc_test_subfolder_one",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
filename: "bar_image.png",
|
|
261
|
+
location: path.join(
|
|
262
|
+
"_sc_test_subfolder_one",
|
|
263
|
+
"subsubfolder",
|
|
264
|
+
"bar_image.png"
|
|
265
|
+
),
|
|
266
|
+
},
|
|
267
|
+
]
|
|
268
|
+
);
|
|
269
|
+
});
|
|
149
270
|
});
|
|
150
271
|
describe("files edit", () => {
|
|
151
272
|
it("creates table and view", async () => {
|