@saltcorn/server 0.8.2 → 0.8.3-alpha.1
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/app.js +1 -1
- package/auth/routes.js +9 -2
- package/auth/testhelp.js +4 -1
- package/locales/en.json +6 -2
- package/locales/ru.json +28 -9
- package/package.json +10 -10
- package/public/diagram_utils.js +4 -3
- package/public/saltcorn-common.js +7 -3
- package/public/saltcorn.css +6 -0
- package/public/saltcorn.js +9 -1
- package/routes/api.js +18 -4
- package/routes/delete.js +3 -2
- package/routes/fields.js +3 -0
- package/routes/menu.js +3 -3
- package/routes/page.js +8 -4
- package/routes/tables.js +6 -3
- package/routes/tenant.js +8 -9
- package/routes/utils.js +1 -1
- package/routes/view.js +4 -0
- package/routes/viewedit.js +21 -10
- package/systemd.js +18 -7
- package/tests/auth.test.js +12 -12
- package/tests/clientjs.test.js +65 -19
- package/tests/files.test.js +1 -0
- package/tests/kittens.test.js +2 -2
- package/tests/table.test.js +4 -4
- package/tests/tenant.test.js +0 -4
- package/tests/view.test.js +5 -3
package/app.js
CHANGED
|
@@ -184,7 +184,7 @@ const getApp = async (opts = {}) => {
|
|
|
184
184
|
new CustomStrategy((req, done) => {
|
|
185
185
|
loginAttempt();
|
|
186
186
|
async function loginAttempt() {
|
|
187
|
-
const { remember, _csrf, ...userobj } = req.body;
|
|
187
|
+
const { remember, _csrf, dest, ...userobj } = req.body;
|
|
188
188
|
if (!is.objVals(is.str).check(userobj))
|
|
189
189
|
return done(
|
|
190
190
|
null,
|
package/auth/routes.js
CHANGED
|
@@ -93,7 +93,7 @@ const loginForm = (req, isCreating) => {
|
|
|
93
93
|
// TBD unresolved usernameLabel
|
|
94
94
|
.map((auth) => `${auth.usernameLabel} for ${auth.label}`)
|
|
95
95
|
.join(", ");
|
|
96
|
-
|
|
96
|
+
const form = new Form({
|
|
97
97
|
class: "login",
|
|
98
98
|
fields: [
|
|
99
99
|
new Field({
|
|
@@ -119,6 +119,12 @@ const loginForm = (req, isCreating) => {
|
|
|
119
119
|
action: "/auth/login",
|
|
120
120
|
submitLabel: req.__("Login"),
|
|
121
121
|
});
|
|
122
|
+
const { dest } = req.query;
|
|
123
|
+
if (dest) {
|
|
124
|
+
form.hidden("dest");
|
|
125
|
+
form.values.dest = encodeURIComponent(dest);
|
|
126
|
+
}
|
|
127
|
+
return form;
|
|
122
128
|
};
|
|
123
129
|
|
|
124
130
|
/**
|
|
@@ -1049,7 +1055,6 @@ router.post(
|
|
|
1049
1055
|
res.redirect("/auth/twofa/login/totp");
|
|
1050
1056
|
return;
|
|
1051
1057
|
}
|
|
1052
|
-
|
|
1053
1058
|
if (req.session.cookie)
|
|
1054
1059
|
if (req.body.remember) {
|
|
1055
1060
|
const setDur = +getState().getConfig("cookie_duration_remember", 0);
|
|
@@ -1070,6 +1075,8 @@ router.post(
|
|
|
1070
1075
|
}
|
|
1071
1076
|
if (getState().get2FApolicy(req.user) === "Mandatory") {
|
|
1072
1077
|
res.redirect("/auth/twofa/setup/totp");
|
|
1078
|
+
} else if (req.body.dest) {
|
|
1079
|
+
res.redirect(decodeURIComponent(req.body.dest));
|
|
1073
1080
|
} else res.redirect("/");
|
|
1074
1081
|
})
|
|
1075
1082
|
);
|
package/auth/testhelp.js
CHANGED
|
@@ -131,7 +131,10 @@ const itShouldRedirectUnauthToLogin = (path, dest) => {
|
|
|
131
131
|
const res = await request(app)
|
|
132
132
|
.get(path)
|
|
133
133
|
.expect(302)
|
|
134
|
-
.expect(
|
|
134
|
+
.expect(
|
|
135
|
+
"Location",
|
|
136
|
+
dest || `/auth/login?dest=${encodeURIComponent(path)}`
|
|
137
|
+
);
|
|
135
138
|
|
|
136
139
|
expect(res.statusCode).toEqual(302);
|
|
137
140
|
});
|
package/locales/en.json
CHANGED
|
@@ -1043,7 +1043,6 @@
|
|
|
1043
1043
|
"Rights settings": "Rights settings",
|
|
1044
1044
|
"Database name": "Database name",
|
|
1045
1045
|
"Database schema": "Database schema",
|
|
1046
|
-
"<p>You have views with a role to access lower than the table role to read, \n with no table ownership. In the next version of Saltcorn, this may cause a\n denial of access. Users will need to have table read access to any data displayed.</p> \n Views potentially affected: %s": "<p>You have views with a role to access lower than the table role to read, \n with no table ownership. In the next version of Saltcorn, this may cause a\n denial of access. Users will need to have table read access to any data displayed.</p> \n Views potentially affected: %s",
|
|
1047
1046
|
"If the parent row is deleted, do this to the child rows.": "If the parent row is deleted, do this to the child rows.",
|
|
1048
1047
|
"On delete": "On delete",
|
|
1049
1048
|
"Permissions": "Permissions",
|
|
@@ -1082,5 +1081,10 @@
|
|
|
1082
1081
|
"Cache-control max-age for public views and pages. 0 to disable": "Cache-control max-age for public views and pages. 0 to disable",
|
|
1083
1082
|
"Files accept filter": "Files accept filter",
|
|
1084
1083
|
"User group": "User group",
|
|
1085
|
-
"Add relations to this table in dropdown options for ownership field": "Add relations to this table in dropdown options for ownership field"
|
|
1084
|
+
"Add relations to this table in dropdown options for ownership field": "Add relations to this table in dropdown options for ownership field",
|
|
1085
|
+
"You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.": "You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.",
|
|
1086
|
+
"Views potentially affected": "Views potentially affected",
|
|
1087
|
+
"Empty view": "Empty view",
|
|
1088
|
+
"A view that will be shown only if there are no tables rows to show": "A view that will be shown only if there are no tables rows to show",
|
|
1089
|
+
"Calculated field will be stored in Database": "Calculated field will be stored in Database"
|
|
1086
1090
|
}
|
package/locales/ru.json
CHANGED
|
@@ -731,7 +731,7 @@
|
|
|
731
731
|
"Forget table": "Забыть таблицу",
|
|
732
732
|
"Ownership formula": "Owner (формула)",
|
|
733
733
|
"User is treated as owner if true. In scope: ": "Ownership formula. Пользователь воспринимается как владелец, если значение формулы true. В рамках: ",
|
|
734
|
-
"This is a translation of a different field in a different language": "Это
|
|
734
|
+
"This is a translation of a different field in a different language": "Это поле является переводом другого поля на другой язык",
|
|
735
735
|
"Language locale of translation": "Языковая локализация (locale) для перевода",
|
|
736
736
|
"Slug": "Slug",
|
|
737
737
|
"Field that can be used for a prettier URL structure": "Поле, которе используется для настройки структуры URL",
|
|
@@ -933,21 +933,40 @@
|
|
|
933
933
|
"%s view - %s on %s": "%s view - %s on %s",
|
|
934
934
|
"Password Repeat": "Повторите пароль",
|
|
935
935
|
"Remember me": "Запомнить меня",
|
|
936
|
-
"Specifies a filter for what file types the user can pick from the file input dialog box. Example is `text/csv,audio/*,video/*,image/*`": "
|
|
936
|
+
"Specifies a filter for what file types the user can pick from the file input dialog box. Example is `text/csv,audio/*,video/*,image/*`": "Определяет через запятую список MIME типов файлов, которые допустимо загружать через диалог загрузки файлов. Пример `text/csv,audio/*,video/*,image/*`",
|
|
937
937
|
"Home Page by Role": "Домашняя страница в соответствие с ролью",
|
|
938
938
|
"Files accept filter": "Фильтр типов файлов при загрузке",
|
|
939
939
|
"Specify how to create a new row": "Укажите как создавать новую строку",
|
|
940
940
|
"Cascade delete to file": "Каскадное удаление к файлу",
|
|
941
|
-
"Deleting a row will also delete the file referenced by this field": "
|
|
942
|
-
"Specifies a filter for what file types the user can pick from the file input dialog box. Example is `.doc,audio/*,video/*,image/*`": "
|
|
943
|
-
"Specifies a default filter for what file types the user can pick from the file input dialog box. Example is `.doc, text/csv,audio/*,video/*,image/*`": "
|
|
941
|
+
"Deleting a row will also delete the file referenced by this field": "При удалении записи также удаляется файл, на который ссылается поле этой записи",
|
|
942
|
+
"Specifies a filter for what file types the user can pick from the file input dialog box. Example is `.doc,audio/*,video/*,image/*`": "Определяет через запятую список MIME типов файлов, которые допустимо загружать через диалог загрузки файлов. Пример `.doc,audio/*,video/*,image/*`",
|
|
943
|
+
"Specifies a default filter for what file types the user can pick from the file input dialog box. Example is `.doc, text/csv,audio/*,video/*,image/*`": "Определяет через запятую список MIME типов файлов, которые допустимо загружать через диалог загрузки файлов. Пример `.doc, text/csv,audio/*,video/*,image/*`",
|
|
944
944
|
"No file found": "Файл не найден",
|
|
945
945
|
"Default File accept filter": "Фильтр типов файлов для загрузки",
|
|
946
946
|
"File upload debug": "Флаг отладки загрузки файлов",
|
|
947
|
-
"Turn on to debug file upload in express-fileupload.": "
|
|
947
|
+
"Turn on to debug file upload in express-fileupload.": "Включить отладочные сообщения при загрузке файлов.",
|
|
948
948
|
"File upload size limit in bytes": "Ограничение на размер файла при загрузке (в байтах)",
|
|
949
|
-
"Defines in bytes limit for upload files in express-fileupload.": "
|
|
949
|
+
"Defines in bytes limit for upload files in express-fileupload.": "Максимальный допустимый размер загружаемого файла (указывается в байтах).",
|
|
950
950
|
"File upload timeout": "Таймаут загрузки файлов",
|
|
951
|
-
"Defines how long to wait for data before aborting for express-fileupload. Set to 0 if you want to turn off timeout checks. ": "
|
|
952
|
-
"Files settings": "Настройки Файлов"
|
|
951
|
+
"Defines how long to wait for data before aborting for express-fileupload. Set to 0 if you want to turn off timeout checks. ": "определяет максимально допустимый интервал времени ожидания загрузки файла. 0 отключает проверку. ",
|
|
952
|
+
"Files settings": "Настройки Файлов",
|
|
953
|
+
"Log client errors": "Логировать ошибки на клиенте",
|
|
954
|
+
"Record all client errors in the crash log": "Записывать все ошибки в приложении-клиенте в журнал сбоев (crash log)",
|
|
955
|
+
"NPM packages in code": "Пакеты NPM для JS кода",
|
|
956
|
+
"Comma-separated list of packages which will be available in JavaScript actions": "Список пакетов через запятую, которые будут использованы в действиях Javascript",
|
|
957
|
+
"Owned": "Принадлежит",
|
|
958
|
+
"You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.": "Настроены представления с уровнем доступа меньшим, чем уровень чтения данных из таблиц. При этом не используется механизм владения таблицей. Это может вызвать конфликт доступа. Пользователи должны иметь доступ ко всем отображаемым данным.",
|
|
959
|
+
"Views potentially affected": "Потенциально затронутые представления",
|
|
960
|
+
"Become user": "Стать пользователем",
|
|
961
|
+
"Public cache TTL (minutes)": "Публичный кеш TTL (минуты)",
|
|
962
|
+
"Cache-control max-age for public views and pages. 0 to disable": "Максимальный возраст кеша для публичных представлений и страниц. 0 отключает проверку",
|
|
963
|
+
"Update": "Обновить",
|
|
964
|
+
"Add": "Добавить",
|
|
965
|
+
"Recalculate dynamic": "Пересчитать динамические элементы",
|
|
966
|
+
"Upload size limit (Kb)": "Лимит размера загружаемых файлов (Kb)",
|
|
967
|
+
"Maximum upload file size in kilobytes": "Максимальный допустимый размер загружаемого файла (указывается в килобайтах)",
|
|
968
|
+
"Defines how long to wait for data before aborting file upload. Set to 0 if you want to turn off timeout checks. ": "Определяет максимальный допустимый интервал времени ожидания загрузки данных файла. 0 отключает проверку. ",
|
|
969
|
+
"User group": "Группа пользователей",
|
|
970
|
+
"Add relations to this table in dropdown options for ownership field": "Добавить связь с этой таблицей в выбор опций для поля Владельца ",
|
|
971
|
+
"Calculated field will be stored in Database": "Вычисляемое будет сохранено в базе данных"
|
|
953
972
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3-alpha.1",
|
|
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.
|
|
10
|
-
"@saltcorn/builder": "0.8.
|
|
11
|
-
"@saltcorn/data": "0.8.
|
|
12
|
-
"@saltcorn/admin-models": "0.8.
|
|
13
|
-
"@saltcorn/filemanager": "0.8.
|
|
14
|
-
"@saltcorn/markup": "0.8.
|
|
15
|
-
"@saltcorn/sbadmin2": "0.8.
|
|
9
|
+
"@saltcorn/base-plugin": "0.8.3-alpha.1",
|
|
10
|
+
"@saltcorn/builder": "0.8.3-alpha.1",
|
|
11
|
+
"@saltcorn/data": "0.8.3-alpha.1",
|
|
12
|
+
"@saltcorn/admin-models": "0.8.3-alpha.1",
|
|
13
|
+
"@saltcorn/filemanager": "0.8.3-alpha.1",
|
|
14
|
+
"@saltcorn/markup": "0.8.3-alpha.1",
|
|
15
|
+
"@saltcorn/sbadmin2": "0.8.3-alpha.1",
|
|
16
16
|
"@socket.io/cluster-adapter": "^0.1.0",
|
|
17
17
|
"@socket.io/sticky": "^1.0.1",
|
|
18
18
|
"aws-sdk": "^2.1037.0",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"greenlock-express": "^4.0.3",
|
|
34
34
|
"helmet": "^3.23.3",
|
|
35
35
|
"i18n": "^0.14.0",
|
|
36
|
-
"jsonwebtoken": "^
|
|
36
|
+
"jsonwebtoken": "^9.0.0",
|
|
37
37
|
"live-plugin-manager": "^0.16.0",
|
|
38
38
|
"moment": "^2.27.0",
|
|
39
39
|
"multer": "^1.4.3",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"passport": "^0.4.1",
|
|
45
45
|
"passport-custom": "^1.1.1",
|
|
46
46
|
"passport-http-bearer": "^1.0.1",
|
|
47
|
-
"passport-jwt": "4.0.
|
|
47
|
+
"passport-jwt": "4.0.1",
|
|
48
48
|
"passport-totp": "0.0.2",
|
|
49
49
|
"pg": "^8.2.1",
|
|
50
50
|
"pluralize": "^8.0.0",
|
package/public/diagram_utils.js
CHANGED
|
@@ -29,7 +29,8 @@ function initMouseOver() {
|
|
|
29
29
|
};
|
|
30
30
|
node.on("position", update);
|
|
31
31
|
cy.on("pan zoom resize", update);
|
|
32
|
-
|
|
32
|
+
const { type } = node.data();
|
|
33
|
+
if (type === "page" || type === "view") buildPreview(node);
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
cy.on("mouseout", "node", (event) => {
|
|
@@ -41,7 +42,7 @@ function initMouseOver() {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function buildCard(node) {
|
|
44
|
-
const { type, label } = node.data();
|
|
45
|
+
const { type, label, isVirtual } = node.data();
|
|
45
46
|
const html = `
|
|
46
47
|
<div class="card" style="width: 20rem;">
|
|
47
48
|
<div class="card-header">
|
|
@@ -49,7 +50,7 @@ function buildCard(node) {
|
|
|
49
50
|
<h6 class="card-subtitle text-muted">${label}</h6>
|
|
50
51
|
</div>
|
|
51
52
|
<div class="card-body">
|
|
52
|
-
${buildTagBadges(node)}
|
|
53
|
+
${!isVirtual ? buildTagBadges(node) : "<h5>virtual</h5>"}
|
|
53
54
|
${buildCardBody(node)}
|
|
54
55
|
<div>
|
|
55
56
|
${type === "page" || type === "view" ? buildPreviewDiv(node) : ""}
|
|
@@ -780,16 +780,19 @@ function unique_field_from_rows(
|
|
|
780
780
|
const vals = rows
|
|
781
781
|
.map((o) => o[field_name])
|
|
782
782
|
.filter((s) => s.startsWith(value));
|
|
783
|
-
|
|
783
|
+
const numtype =
|
|
784
|
+
char_type !== "Lowercase Letters" && char_type !== "Uppercase Letters";
|
|
784
785
|
if (vals.includes(value) || always_append) {
|
|
785
786
|
let newname;
|
|
786
787
|
const stripped = vals
|
|
787
788
|
.filter((v) => v !== value)
|
|
788
789
|
.map((s) => s.replace(value_wspace, ""))
|
|
789
|
-
.
|
|
790
|
+
.map((s) => (numtype ? +s : s))
|
|
791
|
+
.sort(numtype ? (a, b) => a - b : undefined);
|
|
790
792
|
if (stripped.length === 0) newname = `${value_wspace}${gen_char(start)}`;
|
|
791
793
|
else {
|
|
792
|
-
const
|
|
794
|
+
const i = char_to_i(stripped[stripped.length - 1]);
|
|
795
|
+
const last_i = numtype ? Math.max(i, start - 1) : i;
|
|
793
796
|
|
|
794
797
|
newname = `${value_wspace}${gen_char(last_i + 1)}`;
|
|
795
798
|
}
|
|
@@ -872,6 +875,7 @@ function split_paste_handler(e) {
|
|
|
872
875
|
//const pasted =
|
|
873
876
|
$elem.val(lines.shift());
|
|
874
877
|
} else $elem.val(lines.shift());
|
|
878
|
+
$elem.trigger("change");
|
|
875
879
|
}
|
|
876
880
|
});
|
|
877
881
|
}
|
package/public/saltcorn.css
CHANGED
package/public/saltcorn.js
CHANGED
|
@@ -380,8 +380,11 @@ function updateViewPreview() {
|
|
|
380
380
|
"CSRF-Token": _sc_globalCsrf,
|
|
381
381
|
},
|
|
382
382
|
|
|
383
|
-
error: function (
|
|
383
|
+
error: function (resp) {
|
|
384
|
+
$("#viewcfg-preview-error").html(resp.responseText || resp.statusText);
|
|
385
|
+
},
|
|
384
386
|
success: function (res) {
|
|
387
|
+
$("#viewcfg-preview-error").html("");
|
|
385
388
|
$preview.css({ opacity: 1.0 });
|
|
386
389
|
|
|
387
390
|
//disable functions preview migght try to call
|
|
@@ -619,6 +622,11 @@ function build_mobile_app(button) {
|
|
|
619
622
|
});
|
|
620
623
|
}
|
|
621
624
|
|
|
625
|
+
function join_field_clicked(e, fieldPath) {
|
|
626
|
+
$("#inputjoin_field").val(fieldPath);
|
|
627
|
+
apply_showif();
|
|
628
|
+
}
|
|
629
|
+
|
|
622
630
|
(() => {
|
|
623
631
|
const e = document.querySelector("[data-sidebar-toggler]");
|
|
624
632
|
let closed = localStorage.getItem("sidebarClosed") === "true";
|
package/routes/api.js
CHANGED
|
@@ -381,7 +381,10 @@ router.post(
|
|
|
381
381
|
res.status(400).json({ error: errors.join(", ") });
|
|
382
382
|
return;
|
|
383
383
|
}
|
|
384
|
-
const ins_res = await table.tryInsertRow(
|
|
384
|
+
const ins_res = await table.tryInsertRow(
|
|
385
|
+
row,
|
|
386
|
+
req.user || user || { role_id: 10 }
|
|
387
|
+
);
|
|
385
388
|
if (ins_res.error) res.status(400).json(ins_res);
|
|
386
389
|
else res.json(ins_res);
|
|
387
390
|
} else {
|
|
@@ -436,7 +439,11 @@ router.post(
|
|
|
436
439
|
res.status(400).json({ error: errors.join(", ") });
|
|
437
440
|
return;
|
|
438
441
|
}
|
|
439
|
-
const ins_res = await table.tryUpdateRow(
|
|
442
|
+
const ins_res = await table.tryUpdateRow(
|
|
443
|
+
row,
|
|
444
|
+
id,
|
|
445
|
+
user || req.user || { role_id: 10 }
|
|
446
|
+
);
|
|
440
447
|
|
|
441
448
|
if (ins_res.error) res.status(400).json(ins_res);
|
|
442
449
|
else res.json(ins_res);
|
|
@@ -475,8 +482,15 @@ router.delete(
|
|
|
475
482
|
//const fields = await table.getFields();
|
|
476
483
|
const row = req.body;
|
|
477
484
|
//readState(row, fields);
|
|
478
|
-
await table.deleteRows(
|
|
479
|
-
|
|
485
|
+
await table.deleteRows(
|
|
486
|
+
{ [pk_name]: row[pk_name] },
|
|
487
|
+
user || req.user || { role_id: 10 }
|
|
488
|
+
);
|
|
489
|
+
} else
|
|
490
|
+
await table.deleteRows(
|
|
491
|
+
{ id },
|
|
492
|
+
user || req.user || { role_id: 10 }
|
|
493
|
+
);
|
|
480
494
|
res.json({ success: true });
|
|
481
495
|
} catch (e) {
|
|
482
496
|
res.status(400).json({ error: e.message });
|
package/routes/delete.js
CHANGED
|
@@ -36,11 +36,12 @@ router.post(
|
|
|
36
36
|
const table = await Table.findOne({ name: tableName });
|
|
37
37
|
const role = req.user && req.user.id ? req.user.role_id : 10;
|
|
38
38
|
try {
|
|
39
|
-
if (role <= table.min_role_write)
|
|
39
|
+
if (role <= table.min_role_write)
|
|
40
|
+
await table.deleteRows({ id }, req.user || { role_id: 10 });
|
|
40
41
|
else if (table.ownership_field_id && req.user) {
|
|
41
42
|
const row = await table.getRow({ id });
|
|
42
43
|
if (row && table.is_owner(req.user, row))
|
|
43
|
-
await table.deleteRows({ id });
|
|
44
|
+
await table.deleteRows({ id }, req.user || { role_id: 10 });
|
|
44
45
|
else req.flash("error", req.__("Not authorized"));
|
|
45
46
|
} else
|
|
46
47
|
req.flash(
|
package/routes/fields.js
CHANGED
|
@@ -137,6 +137,9 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
|
|
|
137
137
|
new Field({
|
|
138
138
|
label: req.__("Stored"),
|
|
139
139
|
name: "stored",
|
|
140
|
+
sublabel: req.__(
|
|
141
|
+
"Calculated field will be stored in Database"
|
|
142
|
+
),
|
|
140
143
|
type: "Bool",
|
|
141
144
|
disabled: !!id,
|
|
142
145
|
showIf: { calculated: true },
|
package/routes/menu.js
CHANGED
|
@@ -83,10 +83,10 @@ const menuForm = async (req) => {
|
|
|
83
83
|
labelCols: 3,
|
|
84
84
|
noSubmitButton: true,
|
|
85
85
|
additionalButtons: [
|
|
86
|
-
{ label: "Update", id: "btnUpdate", class: "btn btn-primary" },
|
|
87
|
-
{ label: "Add", id: "btnAdd", class: "btn btn-primary" },
|
|
86
|
+
{ label: req.__("Update"), id: "btnUpdate", class: "btn btn-primary" },
|
|
87
|
+
{ label: req.__("Add"), id: "btnAdd", class: "btn btn-primary" },
|
|
88
88
|
{
|
|
89
|
-
label: "Recalculate dynamic",
|
|
89
|
+
label: req.__("Recalculate dynamic"),
|
|
90
90
|
id: "btnRecalc",
|
|
91
91
|
class: "btn btn-primary",
|
|
92
92
|
},
|
package/routes/page.js
CHANGED
|
@@ -61,10 +61,14 @@ router.get(
|
|
|
61
61
|
})
|
|
62
62
|
);
|
|
63
63
|
} else {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.
|
|
64
|
+
if (db_page && !req.user) {
|
|
65
|
+
res.redirect(`/auth/login?dest=${encodeURIComponent(req.originalUrl)}`);
|
|
66
|
+
} else {
|
|
67
|
+
state.log(2, `Page $pagename} not found or not authorized`);
|
|
68
|
+
res
|
|
69
|
+
.status(404)
|
|
70
|
+
.sendWrap(`${pagename} page`, req.__("Page %s not found", pagename));
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
73
|
})
|
|
70
74
|
);
|
package/routes/tables.js
CHANGED
|
@@ -430,6 +430,7 @@ router.get(
|
|
|
430
430
|
label: `<b>${t.name}</b>\n${t.fields
|
|
431
431
|
.map((f) => `${f.name} : ${f.pretty_type}`)
|
|
432
432
|
.join("\n")}`,
|
|
433
|
+
title: t.description? t.description : t.name,
|
|
433
434
|
})),
|
|
434
435
|
edges,
|
|
435
436
|
};
|
|
@@ -439,7 +440,7 @@ router.get(
|
|
|
439
440
|
headers: [
|
|
440
441
|
{
|
|
441
442
|
script:
|
|
442
|
-
"https://unpkg.com/vis-network@9.
|
|
443
|
+
"https://unpkg.com/vis-network@9.1.2/standalone/umd/vis-network.min.js",
|
|
443
444
|
},
|
|
444
445
|
],
|
|
445
446
|
},
|
|
@@ -887,8 +888,10 @@ router.post(
|
|
|
887
888
|
"exttables_min_role_read",
|
|
888
889
|
exttables_min_role_read
|
|
889
890
|
);
|
|
890
|
-
|
|
891
|
-
|
|
891
|
+
if (!req.xhr) {
|
|
892
|
+
req.flash("success", req.__("Table saved"));
|
|
893
|
+
res.redirect(`/table/${table.name}`);
|
|
894
|
+
} else res.json({ success: "ok" });
|
|
892
895
|
}
|
|
893
896
|
} else {
|
|
894
897
|
const { id, _csrf, ...rest } = v;
|
package/routes/tenant.js
CHANGED
|
@@ -279,18 +279,17 @@ router.post(
|
|
|
279
279
|
const tenant_template = getState().getConfig("tenant_template");
|
|
280
280
|
// tenant creator
|
|
281
281
|
const user_email = req.user && req.user.email;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
description,
|
|
288
|
-
tenant_template
|
|
289
|
-
),
|
|
290
|
-
newurl
|
|
282
|
+
const tenrow = await insertTenant(
|
|
283
|
+
subdomain,
|
|
284
|
+
user_email,
|
|
285
|
+
description,
|
|
286
|
+
tenant_template
|
|
291
287
|
);
|
|
292
288
|
// add tenant to global state
|
|
293
289
|
add_tenant(subdomain);
|
|
290
|
+
|
|
291
|
+
await switchToTenant(tenrow, newurl);
|
|
292
|
+
|
|
294
293
|
await create_tenant({
|
|
295
294
|
t: subdomain,
|
|
296
295
|
plugin_loader: loadAllPlugins,
|
package/routes/utils.js
CHANGED
package/routes/view.js
CHANGED
|
@@ -57,6 +57,10 @@ router.get(
|
|
|
57
57
|
role > view.min_role &&
|
|
58
58
|
!(await view.authorise_get({ query, req, ...view }))
|
|
59
59
|
) {
|
|
60
|
+
if (!req.user) {
|
|
61
|
+
res.redirect(`/auth/login?dest=${encodeURIComponent(req.originalUrl)}`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
60
64
|
req.flash("danger", req.__("Not authorized"));
|
|
61
65
|
state.log(2, `View ${viewname} not authorized`);
|
|
62
66
|
res.redirect("/");
|
package/routes/viewedit.js
CHANGED
|
@@ -8,7 +8,15 @@
|
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
9
|
|
|
10
10
|
const { renderForm, renderBuilder, alert } = require("@saltcorn/markup");
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
p,
|
|
13
|
+
a,
|
|
14
|
+
div,
|
|
15
|
+
script,
|
|
16
|
+
text,
|
|
17
|
+
domReady,
|
|
18
|
+
pre,
|
|
19
|
+
} = require("@saltcorn/markup/tags");
|
|
12
20
|
|
|
13
21
|
const { getState } = require("@saltcorn/data/db/state");
|
|
14
22
|
const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
|
|
@@ -59,6 +67,7 @@ router.get(
|
|
|
59
67
|
const viewAccessWarning = (view) => {
|
|
60
68
|
const table = tables.find((t) => t.name === view.table);
|
|
61
69
|
if (!table) return false;
|
|
70
|
+
if (table.name === "users") return false;
|
|
62
71
|
if (table.ownership_field_id || table.ownership_formula) return false;
|
|
63
72
|
|
|
64
73
|
return table.min_role_read < view.min_role;
|
|
@@ -68,13 +77,12 @@ router.get(
|
|
|
68
77
|
hasAccessWarning.length > 0
|
|
69
78
|
? alert(
|
|
70
79
|
"danger",
|
|
71
|
-
req.__(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
80
|
+
`<p>${req.__(
|
|
81
|
+
`You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.`
|
|
82
|
+
)}</p>
|
|
83
|
+
${req.__("Views potentially affected")}: ${hasAccessWarning
|
|
84
|
+
.map((v) => v.name)
|
|
85
|
+
.join(", ")}`
|
|
78
86
|
)
|
|
79
87
|
: "";
|
|
80
88
|
res.sendWrap(req.__(`Views`), {
|
|
@@ -481,8 +489,11 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
|
|
|
481
489
|
type: "card",
|
|
482
490
|
title: req.__("Preview"),
|
|
483
491
|
contents: div(
|
|
484
|
-
{ id: "viewcfg-preview", "
|
|
485
|
-
|
|
492
|
+
div(pre({ id: "viewcfg-preview-error", class: "text-danger" })),
|
|
493
|
+
div(
|
|
494
|
+
{ id: "viewcfg-preview", "data-preview-url": previewURL },
|
|
495
|
+
script(domReady(`updateViewPreview()`))
|
|
496
|
+
)
|
|
486
497
|
),
|
|
487
498
|
},
|
|
488
499
|
]
|
package/systemd.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @module systemd
|
|
4
4
|
*/
|
|
5
5
|
const fetch = require("node-fetch");
|
|
6
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {number} interval
|
|
@@ -21,29 +22,33 @@ const watchDog = (interval, notify, { port }) => {
|
|
|
21
22
|
const User = require("@saltcorn/data/models/user");
|
|
22
23
|
User.count()
|
|
23
24
|
.then((c) => {
|
|
24
|
-
|
|
25
|
+
getState().log(5, `watchdog user count ${c}`);
|
|
25
26
|
notify.watchdog();
|
|
26
27
|
})
|
|
27
28
|
.catch((e) => {
|
|
28
|
-
|
|
29
|
+
getState().log(1, e);
|
|
29
30
|
process.exit(1);
|
|
30
31
|
});
|
|
31
32
|
} else {
|
|
32
33
|
// check we can serve
|
|
33
34
|
fetch(`http://127.0.0.1:${port}/auth/login`)
|
|
34
35
|
.then((response) => {
|
|
35
|
-
|
|
36
|
+
getState().log(5, `watchdog request status ${response.status}`);
|
|
36
37
|
if (response.status < 400) notify.watchdog();
|
|
37
38
|
else process.exit(1);
|
|
38
39
|
})
|
|
39
40
|
.catch((e) => {
|
|
40
|
-
|
|
41
|
+
getState().log(1, e);
|
|
41
42
|
process.exit(1);
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
|
-
} else
|
|
45
|
+
} else {
|
|
46
|
+
getState().log(5, `watchdog with no test`);
|
|
47
|
+
notify.watchdog();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
45
50
|
} catch (e) {
|
|
46
|
-
|
|
51
|
+
getState().log(1, e);
|
|
47
52
|
process.exit(1);
|
|
48
53
|
}
|
|
49
54
|
};
|
|
@@ -57,6 +62,7 @@ module.exports =
|
|
|
57
62
|
(opts) => {
|
|
58
63
|
try {
|
|
59
64
|
const notify = require("sd-notify");
|
|
65
|
+
getState().log(4, `systemd notify ready`);
|
|
60
66
|
notify.ready();
|
|
61
67
|
const watchdogInterval = notify.watchdogInterval();
|
|
62
68
|
if (watchdogInterval && watchdogInterval > 0) {
|
|
@@ -65,7 +71,12 @@ module.exports =
|
|
|
65
71
|
watchDog(interval, notify, opts);
|
|
66
72
|
}, interval);
|
|
67
73
|
}
|
|
68
|
-
} catch {
|
|
74
|
+
} catch (e) {
|
|
69
75
|
//ignore, systemd lib not installed
|
|
76
|
+
getState().log(
|
|
77
|
+
4,
|
|
78
|
+
`Failed to notify systemd on startup (systemd lib not installed?) with error ${e}`
|
|
79
|
+
);
|
|
70
80
|
}
|
|
71
81
|
};
|
|
82
|
+
4;
|
package/tests/auth.test.js
CHANGED
|
@@ -18,7 +18,7 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
18
18
|
const { get_reset_link, generate_email } = require("../auth/resetpw");
|
|
19
19
|
const i18n = require("i18n");
|
|
20
20
|
const path = require("path");
|
|
21
|
-
const fs = require("fs")
|
|
21
|
+
const fs = require("fs");
|
|
22
22
|
|
|
23
23
|
afterAll(db.close);
|
|
24
24
|
beforeAll(async () => {
|
|
@@ -606,19 +606,19 @@ describe("signup with custom login form", () => {
|
|
|
606
606
|
|
|
607
607
|
describe("Locale files", () => {
|
|
608
608
|
it("should be valid JSON", async () => {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
expect(localeFiles.length).toBeGreaterThan(3)
|
|
613
|
-
expect(localeFiles).toContain("en.json")
|
|
609
|
+
const localeFiles = await fs.promises.readdir(
|
|
610
|
+
path.join(__dirname, "..", "/locales")
|
|
611
|
+
);
|
|
612
|
+
expect(localeFiles.length).toBeGreaterThan(3);
|
|
613
|
+
expect(localeFiles).toContain("en.json");
|
|
614
614
|
for (const fnm of localeFiles) {
|
|
615
615
|
const conts = await fs.promises.readFile(
|
|
616
616
|
path.join(__dirname, "..", "/locales", fnm)
|
|
617
|
-
)
|
|
618
|
-
expect(conts.length).toBeGreaterThan(1)
|
|
617
|
+
);
|
|
618
|
+
expect(conts.length).toBeGreaterThan(1);
|
|
619
619
|
|
|
620
|
-
const j = JSON.parse(conts)
|
|
621
|
-
expect(Object.keys(j).length).toBeGreaterThan(1)
|
|
620
|
+
const j = JSON.parse(conts);
|
|
621
|
+
expect(Object.keys(j).length).toBeGreaterThan(1);
|
|
622
622
|
}
|
|
623
|
-
})
|
|
624
|
-
})
|
|
623
|
+
});
|
|
624
|
+
});
|
package/tests/clientjs.test.js
CHANGED
|
@@ -65,10 +65,10 @@ test("updateQueryStringParameter hash", () => {
|
|
|
65
65
|
);
|
|
66
66
|
});
|
|
67
67
|
test("unique_field_from_rows test", () => {
|
|
68
|
-
$("body").append(`<input id="
|
|
68
|
+
$("body").append(`<input id="mkuniq6" value="bar"></div>`);
|
|
69
69
|
unique_field_from_rows(
|
|
70
70
|
[{ foo: "bar" }, { foo: "bar0" }],
|
|
71
|
-
"
|
|
71
|
+
"mkuniq6",
|
|
72
72
|
"foo",
|
|
73
73
|
false,
|
|
74
74
|
0,
|
|
@@ -76,12 +76,12 @@ test("unique_field_from_rows test", () => {
|
|
|
76
76
|
"Digits",
|
|
77
77
|
"bar"
|
|
78
78
|
);
|
|
79
|
-
expect($("#
|
|
79
|
+
expect($("#mkuniq6").val()).toBe("bar1");
|
|
80
80
|
|
|
81
|
-
$("body").append(`<input id="
|
|
81
|
+
$("body").append(`<input id="mkuniq5" value="bar"></div>`);
|
|
82
82
|
unique_field_from_rows(
|
|
83
83
|
[{ foo: "bar" }],
|
|
84
|
-
"
|
|
84
|
+
"mkuniq5",
|
|
85
85
|
"foo",
|
|
86
86
|
false,
|
|
87
87
|
9,
|
|
@@ -89,12 +89,12 @@ test("unique_field_from_rows test", () => {
|
|
|
89
89
|
"Digits",
|
|
90
90
|
"bar"
|
|
91
91
|
);
|
|
92
|
-
expect($("#
|
|
92
|
+
expect($("#mkuniq5").val()).toBe("bar9");
|
|
93
93
|
|
|
94
|
-
$("body").append(`<input id="
|
|
94
|
+
$("body").append(`<input id="mkuniq4" value="bar"></div>`);
|
|
95
95
|
unique_field_from_rows(
|
|
96
|
-
[{ foo: "bar0" }],
|
|
97
|
-
"
|
|
96
|
+
[{ foo: "bar" }, { foo: "bar0" }],
|
|
97
|
+
"mkuniq4",
|
|
98
98
|
"foo",
|
|
99
99
|
false,
|
|
100
100
|
9,
|
|
@@ -102,16 +102,25 @@ test("unique_field_from_rows test", () => {
|
|
|
102
102
|
"Digits",
|
|
103
103
|
"bar"
|
|
104
104
|
);
|
|
105
|
-
expect($("#
|
|
105
|
+
expect($("#mkuniq4").val()).toBe("bar9");
|
|
106
106
|
|
|
107
|
-
$("#
|
|
108
|
-
unique_field_from_rows(
|
|
109
|
-
|
|
107
|
+
$("#mkuniq6").val("bar");
|
|
108
|
+
unique_field_from_rows(
|
|
109
|
+
[],
|
|
110
|
+
"mkuniq6",
|
|
111
|
+
"foo",
|
|
112
|
+
false,
|
|
113
|
+
0,
|
|
114
|
+
false,
|
|
115
|
+
"Digits",
|
|
116
|
+
"bar"
|
|
117
|
+
);
|
|
118
|
+
expect($("#mkuniq6").val()).toBe("bar");
|
|
110
119
|
|
|
111
|
-
$("body").append(`<input id="
|
|
120
|
+
$("body").append(`<input id="mkuniq3" value="bar"></div>`);
|
|
112
121
|
unique_field_from_rows(
|
|
113
122
|
[{ foo: "bar" }, { foo: "bar A" }],
|
|
114
|
-
"
|
|
123
|
+
"mkuniq3",
|
|
115
124
|
"foo",
|
|
116
125
|
true,
|
|
117
126
|
0,
|
|
@@ -119,13 +128,13 @@ test("unique_field_from_rows test", () => {
|
|
|
119
128
|
"Uppercase Letters",
|
|
120
129
|
"bar"
|
|
121
130
|
);
|
|
122
|
-
expect($("#
|
|
131
|
+
expect($("#mkuniq3").val()).toBe("bar B");
|
|
123
132
|
|
|
124
133
|
//skips blanks
|
|
125
|
-
$("body").append(`<input id="
|
|
134
|
+
$("body").append(`<input id="mkuniq2" value="bar"></div>`);
|
|
126
135
|
unique_field_from_rows(
|
|
127
136
|
[{ foo: "bar" }, { foo: "bar0" }, { foo: "bar1" }, { foo: "bar3" }],
|
|
128
|
-
"
|
|
137
|
+
"mkuniq2",
|
|
129
138
|
"foo",
|
|
130
139
|
false,
|
|
131
140
|
0,
|
|
@@ -133,5 +142,42 @@ test("unique_field_from_rows test", () => {
|
|
|
133
142
|
"Digits",
|
|
134
143
|
"bar"
|
|
135
144
|
);
|
|
136
|
-
expect($("#
|
|
145
|
+
expect($("#mkuniq2").val()).toBe("bar4");
|
|
146
|
+
|
|
147
|
+
$("body").append(`<input id="mkuniq1" value="bar"></div>`);
|
|
148
|
+
unique_field_from_rows(
|
|
149
|
+
[{ foo: "bar100" }, { foo: "bar101" }, { foo: "bar103" }],
|
|
150
|
+
"mkuniq1",
|
|
151
|
+
"foo",
|
|
152
|
+
false,
|
|
153
|
+
100,
|
|
154
|
+
true,
|
|
155
|
+
"Digits",
|
|
156
|
+
"bar"
|
|
157
|
+
);
|
|
158
|
+
expect($("#mkuniq1").val()).toBe("bar104");
|
|
159
|
+
$("body").append(`<input id="mkuniq10" value="bar"></div>`);
|
|
160
|
+
unique_field_from_rows(
|
|
161
|
+
[],
|
|
162
|
+
"mkuniq10",
|
|
163
|
+
"foo",
|
|
164
|
+
false,
|
|
165
|
+
100,
|
|
166
|
+
true,
|
|
167
|
+
"Digits",
|
|
168
|
+
"bar"
|
|
169
|
+
);
|
|
170
|
+
expect($("#mkuniq10").val()).toBe("bar100");
|
|
171
|
+
$("body").append(`<input id="mkuniq11" value="bar"></div>`);
|
|
172
|
+
unique_field_from_rows(
|
|
173
|
+
[{ foo: "bar100" }, { foo: "bar101" }, { foo: "bar35" }, { foo: "bar103" }],
|
|
174
|
+
"mkuniq11",
|
|
175
|
+
"foo",
|
|
176
|
+
false,
|
|
177
|
+
100,
|
|
178
|
+
true,
|
|
179
|
+
"Digits",
|
|
180
|
+
"bar"
|
|
181
|
+
);
|
|
182
|
+
expect($("#mkuniq11").val()).toBe("bar104");
|
|
137
183
|
});
|
package/tests/files.test.js
CHANGED
|
@@ -109,6 +109,7 @@ describe("files admin", () => {
|
|
|
109
109
|
describe("files edit", () => {
|
|
110
110
|
it("creates table and view", async () => {
|
|
111
111
|
const table = await Table.create("thefiletable");
|
|
112
|
+
await table.update({ min_role_read: 8, min_role_write: 8 });
|
|
112
113
|
await Field.create({
|
|
113
114
|
table,
|
|
114
115
|
name: "first_name",
|
package/tests/kittens.test.js
CHANGED
package/tests/table.test.js
CHANGED
|
@@ -33,9 +33,9 @@ describe("Table Endpoints", () => {
|
|
|
33
33
|
.post("/table/")
|
|
34
34
|
.send("name=mypostedtable")
|
|
35
35
|
.set("Cookie", loginCookie)
|
|
36
|
-
.expect(toRedirect("/table/
|
|
36
|
+
.expect(toRedirect("/table/10"));
|
|
37
37
|
await request(app)
|
|
38
|
-
.get("/table/
|
|
38
|
+
.get("/table/10")
|
|
39
39
|
.set("Cookie", loginCookie)
|
|
40
40
|
.expect(toInclude("mypostedtable"));
|
|
41
41
|
await request(app)
|
|
@@ -114,7 +114,7 @@ describe("Table Endpoints", () => {
|
|
|
114
114
|
});
|
|
115
115
|
it("should edit external table role", async () => {
|
|
116
116
|
const loginCookie = await getAdminLoginCookie();
|
|
117
|
-
getState().registerPlugin("mock_plugin", plugin_with_routes);
|
|
117
|
+
getState().registerPlugin("mock_plugin", plugin_with_routes());
|
|
118
118
|
const app = await getApp({ disableCsrf: true });
|
|
119
119
|
await request(app)
|
|
120
120
|
.post(`/table`)
|
|
@@ -149,7 +149,7 @@ Pencil, 0.5,2, t`;
|
|
|
149
149
|
.set("Cookie", loginCookie)
|
|
150
150
|
.field("name", "expenses")
|
|
151
151
|
.attach("file", Buffer.from(csv, "utf-8"))
|
|
152
|
-
.expect(toRedirect("/table/
|
|
152
|
+
.expect(toRedirect("/table/11"));
|
|
153
153
|
});
|
|
154
154
|
it("should upload csv to existing table", async () => {
|
|
155
155
|
const csv = `author,Pages
|
package/tests/tenant.test.js
CHANGED
|
@@ -16,7 +16,6 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
16
16
|
afterAll(db.close);
|
|
17
17
|
jest.setTimeout(10000);
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
beforeAll(async () => {
|
|
21
20
|
if (!db.isSQLite) {
|
|
22
21
|
await db.query(`drop schema if exists test2 cascade`);
|
|
@@ -94,12 +93,9 @@ describe("tenant routes", () => {
|
|
|
94
93
|
.set("Cookie", loginCookie)
|
|
95
94
|
.expect(toRedirect("/tenant/list"));
|
|
96
95
|
});
|
|
97
|
-
|
|
98
96
|
} else {
|
|
99
|
-
|
|
100
97
|
it("does not support tenants on SQLite", async () => {
|
|
101
98
|
expect(db.isSQLite).toBe(true);
|
|
102
99
|
});
|
|
103
|
-
|
|
104
100
|
}
|
|
105
101
|
});
|
package/tests/view.test.js
CHANGED
|
@@ -2,6 +2,7 @@ const request = require("supertest");
|
|
|
2
2
|
const getApp = require("../app");
|
|
3
3
|
const {
|
|
4
4
|
toRedirect,
|
|
5
|
+
getAdminLoginCookie,
|
|
5
6
|
getStaffLoginCookie,
|
|
6
7
|
itShouldRedirectUnauthToLogin,
|
|
7
8
|
toInclude,
|
|
@@ -33,7 +34,7 @@ describe("nonexisting view", () => {
|
|
|
33
34
|
itShouldRedirectUnauthToLogin("/view/patlist", "/");
|
|
34
35
|
});
|
|
35
36
|
describe("view patients list endpoint", () => {
|
|
36
|
-
itShouldRedirectUnauthToLogin("/view/patientlist"
|
|
37
|
+
itShouldRedirectUnauthToLogin("/view/patientlist");
|
|
37
38
|
|
|
38
39
|
it("should show view to staff", async () => {
|
|
39
40
|
const loginCookie = await getStaffLoginCookie();
|
|
@@ -78,17 +79,18 @@ describe("edit view", () => {
|
|
|
78
79
|
});
|
|
79
80
|
it("should submit edit", async () => {
|
|
80
81
|
const app = await getApp({ disableCsrf: true });
|
|
82
|
+
const loginCookie = await getAdminLoginCookie();
|
|
81
83
|
await request(app)
|
|
82
84
|
.post("/view/authoredit")
|
|
85
|
+
.set("Cookie", loginCookie)
|
|
83
86
|
.send("author=Chekov")
|
|
84
|
-
|
|
85
87
|
.expect(toRedirect("/view/authorlist"));
|
|
86
88
|
});
|
|
87
89
|
});
|
|
88
90
|
|
|
89
91
|
describe("view with routes", () => {
|
|
90
92
|
it("should enable", async () => {
|
|
91
|
-
getState().registerPlugin("mock_plugin", plugin_with_routes);
|
|
93
|
+
getState().registerPlugin("mock_plugin", plugin_with_routes());
|
|
92
94
|
expect(getState().viewtemplates.ViewWithRoutes.name).toBe("ViewWithRoutes");
|
|
93
95
|
const table = await Table.findOne({ name: "books" });
|
|
94
96
|
|