@saltcorn/server 0.7.2-beta.0 → 0.7.2-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/app.js +64 -9
- package/auth/routes.js +37 -13
- package/load_plugins.js +29 -24
- package/locales/da.json +1 -1
- package/locales/de.json +155 -155
- package/locales/en.json +8 -5
- package/locales/it.json +1 -1
- package/locales/ru.json +52 -18
- package/locales/zh.json +3 -3
- package/package.json +11 -8
- package/public/saltcorn-common.js +105 -0
- package/public/saltcorn.css +61 -0
- package/public/saltcorn.js +55 -83
- package/routes/admin.js +1 -1
- package/routes/api.js +36 -1
- package/routes/edit.js +2 -1
- package/routes/fields.js +12 -0
- package/routes/viewedit.js +9 -0
- package/tests/admin.test.js +72 -1
- package/tests/clientjs.test.js +1 -0
- package/tests/viewedit.test.js +94 -0
- package/wrapper.js +1 -0
package/locales/ru.json
CHANGED
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"Versions": "Версии",
|
|
145
145
|
"Version": "Версия",
|
|
146
146
|
"Saved": "Сохранено",
|
|
147
|
-
"By user ID": "
|
|
147
|
+
"By user ID": "ID пользователя",
|
|
148
148
|
"%s History": " %s История",
|
|
149
149
|
"back to table list": "возврат к списку таблиц",
|
|
150
150
|
"Version %s restored": "Версия %s восстановлена",
|
|
@@ -273,7 +273,7 @@
|
|
|
273
273
|
"View configuration": "Настройки представления",
|
|
274
274
|
"Field %s deleted": "Поле %s удалено",
|
|
275
275
|
"No tables defined": "Таблицы не определены",
|
|
276
|
-
"Tables hold collections of similar data": "
|
|
276
|
+
"Tables hold collections of similar data": "Таблицы содержат коллекции похожих данных",
|
|
277
277
|
"Invalid form data, try again": "Форма содержит некорректные данные, попробуйте еще раз",
|
|
278
278
|
"Role required to access added files": "Для доступа к добавленными полям требуется роль",
|
|
279
279
|
"The user uploading the file has access irrespective of their role": "Пользователь, загружающий файл, имеет доступ независимо от своей роли",
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
"new": "новый",
|
|
295
295
|
"Password does not match": "Пароль не совпадает",
|
|
296
296
|
"Not allowed to write to table %s": "Запрещена запись в таблицу %s",
|
|
297
|
-
"Login
|
|
297
|
+
"Login successful": "Вход в систему выполнен",
|
|
298
298
|
"Welcome, %s!": "Добро пожаловать, %s!",
|
|
299
299
|
"Error: missing name or file": "Ошибка: отсутствует имя или файл",
|
|
300
300
|
"Restart server.": "Перезагрузить сервер.",
|
|
@@ -305,7 +305,7 @@
|
|
|
305
305
|
"Duplicate": "Дублировать",
|
|
306
306
|
"View %s duplicated as %s": "Представление %s задублировано в %s",
|
|
307
307
|
"The view name will appear as the title of pop-ups showing this view, and in the URL when it is shown alone.": "Имя представления отображается как заголовок всплывающего окна или в URL, в зависимости от режима отображения.",
|
|
308
|
-
"Saltcorn version": "версия
|
|
308
|
+
"Saltcorn version": "версия платформы",
|
|
309
309
|
"Node.js version": "версия Node.js",
|
|
310
310
|
"Table not found": "Таблица не найдена",
|
|
311
311
|
"No record selected": "Не выбрано ни одной записи",
|
|
@@ -535,7 +535,7 @@
|
|
|
535
535
|
"Role to create tenants": "Роль, которой разрешено создание тенатов",
|
|
536
536
|
"Minimum user role required to create a new tenant": "Минимальная роль для создания тенантов",
|
|
537
537
|
"Create tenant warning": "Предупреждение при создании тенанта",
|
|
538
|
-
"Show a warning to users creating a tenant disclaiming warranty of availability or security": "Показывать
|
|
538
|
+
"Show a warning to users creating a tenant disclaiming warranty of availability or security": "Показывать пользователю, создающему тенант, предупреждения о доступности и безопасности",
|
|
539
539
|
"Name of the field": "Название поля",
|
|
540
540
|
"The type determines the kind of data that can be stored in the field": "Тип поля определяет состав данных, которые могут быть сохранены в полей",
|
|
541
541
|
"Calculated from other fields with a formula": "Вычисляется по формуле. В формуле можно использовать другие поля для вычисления",
|
|
@@ -678,16 +678,16 @@
|
|
|
678
678
|
"Home Timezone": "Домашняя Timezone",
|
|
679
679
|
"2FA policy": "2FA policy",
|
|
680
680
|
"Role to generate API keys": "Роль для API keys",
|
|
681
|
-
"User should have this role or higher to generate API keys in their user settings": "
|
|
681
|
+
"User should have this role or higher to generate API keys in their user settings": "Минимальная роль для генерации API key",
|
|
682
682
|
"Cookie duration (hours)": "Срок действия Cookie (в часах)",
|
|
683
|
-
"Set to 0 for expiration at the end of browser session": "
|
|
683
|
+
"Set to 0 for expiration at the end of browser session": "Установите значение 0 чтобы срок действия соответствовал окончанию сессии пользователя",
|
|
684
684
|
"Cookie duration (hours) when remember ticked": "Срок действия Cookie (в часах), когда установлен флажок \"Запомнить\"",
|
|
685
|
-
"Order field": "Order
|
|
686
|
-
"Section field": "Section
|
|
685
|
+
"Order field": "Order (поле)",
|
|
686
|
+
"Section field": "Section (поле))",
|
|
687
687
|
"Optional. String type with options, each of which will become a menu section": "Указывать необязательно. Строковый тип с набором значений, каждая из которых станет секцией меню",
|
|
688
|
-
"Label formula": "Label
|
|
689
|
-
"URL formula": "URL формула",
|
|
690
|
-
"Include formula": "Include
|
|
688
|
+
"Label formula": "Label (формула)",
|
|
689
|
+
"URL formula": "URL (формула)",
|
|
690
|
+
"Include formula": "Include (формула)",
|
|
691
691
|
"If specified, only include in menu rows that evaluate to true": "Если указано, то в меню отображаются только секции (строки), для которых формула вычислилась со значением true",
|
|
692
692
|
"Not all themes support all locations": "Некоторые темы могут не поддерживать все языки",
|
|
693
693
|
"Library": "Библиотека",
|
|
@@ -726,12 +726,12 @@
|
|
|
726
726
|
"Use Amazon S3 Secure Connection.": "Использовать безопасное подключение к Amazon S3.",
|
|
727
727
|
"Connect to Amazon S3 (or compatible) securely.": "Подключиться к сервису Amazon S3 (или совместимому) безопасно.",
|
|
728
728
|
"Storage settings": "Настройки хранилища",
|
|
729
|
-
"Two-factor authentication": "Двухфакторная аутентификация",
|
|
729
|
+
"Two-factor authentication": "Двухфакторная аутентификация (TWA)",
|
|
730
730
|
"Two-factor authentication is disabled": "Двухфакторная аутентификация отключена",
|
|
731
731
|
"Forget table": "Забыть таблицу",
|
|
732
|
-
"Ownership formula": "
|
|
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": "Language locale of translation",
|
|
736
736
|
"Slug": "Slug",
|
|
737
737
|
"Field that can be used for a prettier URL structure": "Поле, которе используется для настройки структуры URL",
|
|
@@ -743,10 +743,44 @@
|
|
|
743
743
|
"Display one column per line": "Отображать один столбец на одной строке",
|
|
744
744
|
"Vertical column width": "Высота столбца",
|
|
745
745
|
"Vertical width units": "Единицы измерения высоты столбца",
|
|
746
|
-
"Preset %s": "
|
|
747
|
-
"Edit properties": "
|
|
746
|
+
"Preset %s": "Установить %s",
|
|
747
|
+
"Edit properties": "Изменить свойства",
|
|
748
748
|
"Yes": "Да",
|
|
749
749
|
"Event type which runs the trigger": "Тип события, которое запускает триггер",
|
|
750
750
|
"Leave blank for all channels": "Если не заполнено, используются все каналы",
|
|
751
|
-
"User must have this role or higher to make API call for action (trigger)": "Пользователь должен иметь указанную роль и выше для выполнение вызова API действия (триггера)"
|
|
751
|
+
"User must have this role or higher to make API call for action (trigger)": "Пользователь должен иметь указанную роль и выше для выполнение вызова API действия (триггера)",
|
|
752
|
+
"New tenant template": "Шаблон нового тенанта",
|
|
753
|
+
"Copy site structure for new tenants from this tenant": "Создавать новые тенанты на основе структуры указанного тенанта",
|
|
754
|
+
"Strings": "Строки",
|
|
755
|
+
"In default language": "В языке по-умолчанию",
|
|
756
|
+
"In %s": "В %s языке",
|
|
757
|
+
"Is this the default language in which the application is built?": "Язык для использования по-умолчанию",
|
|
758
|
+
"Table %s forgotten. You can now discover it.": "Таблица %s забыта системой. Вы можете ее восстановить через механизм обнаружения.",
|
|
759
|
+
"Configuration check": "Проверка конфигурации",
|
|
760
|
+
"Check for updates": "Проверка обновления",
|
|
761
|
+
"Configuration errors": "Ошибки в конфигурации",
|
|
762
|
+
"Configuration checks passed": "Проверка конфигурации пройдена успешно",
|
|
763
|
+
"No errors detected during configuration check": "Не обнаружено ошибок при проверке конфигурации",
|
|
764
|
+
"Extra state Formula": "Extra state Formula",
|
|
765
|
+
"Place in dropdown": "Поместить в dropdown",
|
|
766
|
+
"Hide null columns": "Скрыть стоблцы без значения (null)",
|
|
767
|
+
"Do not display a column if it contains entirely missing values": "Скрыть (не отображать) стоблцы, имеющие значение null",
|
|
768
|
+
"Finish": "Завершить",
|
|
769
|
+
"Versions refreshed": "Версии обновлены",
|
|
770
|
+
"Plugin name": "Имя плагина",
|
|
771
|
+
"Source of plugin for install. Few options:npm - download from npm repository,local - get from local file system,github - download from github,git - get from git": "Source of plugin for install. Few options:npm - download from npm repository,local - get from local file system,github - download from github,git - get from git",
|
|
772
|
+
"For npm - name of npm package, e.g. @saltcorn/html or saltcorn-gantt, check at npmjs.com, for local - absolute path to plugin folder in file system, e.g.C:\\gitsrc\\any-bootstrap-theme\\, for github - name of github project.": "For npm - name of npm package, e.g. @saltcorn/html or saltcorn-gantt, check at npmjs.com, for local - absolute path to plugin folder in file system, e.g.C:\\gitsrc\\any-bootstrap-theme\\, for github - name of github project.",
|
|
773
|
+
"Version of plugin, latest is default value": "Версия плагина, значение по-умолчанию - latest",
|
|
774
|
+
"Private SSH key": "Приватный ключ SSH",
|
|
775
|
+
"Setup two-factor authentication": "Настройть двухфакторную аутентификацию",
|
|
776
|
+
"Setup two-factor authentication with Time-based One-Time Password (TOTP)": "Настроить двухфакторную аутентфикацию с применением Time-based One-Time Password (TOTP)",
|
|
777
|
+
"1. Scan this QR code in your Authenticator app": "1. Отсканируйте данный QR код в удобном Вам приложении Аутентфикатор (Authenticator)",
|
|
778
|
+
"2. Enter the six-digit code generated in your Authenticator app": "2. Введите 6-ти значный секретный код, сгенерированный в приложении Аутентификатор (Authenticator)",
|
|
779
|
+
"Code": "Код",
|
|
780
|
+
"Or enter this code:": "Или введите этот код:",
|
|
781
|
+
"Two-factor authentication with Time-based One-Time Password enabled": "Двухфакторная аутентификаиця с использованием Time-based One-Time Password",
|
|
782
|
+
"Two-factor authentication is enabled": "Двухфакторная аутентификация включена",
|
|
783
|
+
"Disable TWA": "Отключить TWA",
|
|
784
|
+
"Enable TWA": "Включить TWA",
|
|
785
|
+
"Deleted all rows": "Удалены все строки"
|
|
752
786
|
}
|
package/locales/zh.json
CHANGED
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
"new": "新的",
|
|
295
295
|
"Password does not match": "密码不匹配",
|
|
296
296
|
"Not allowed to write to table %s": "不允许写入表 %s",
|
|
297
|
-
"Login
|
|
297
|
+
"Login successful": "登录成功",
|
|
298
298
|
"Welcome, %s!": "欢迎,%s!",
|
|
299
299
|
"Error: missing name or file": "错误:缺少名称或文件",
|
|
300
300
|
"Restart server.": "重启服务器。",
|
|
@@ -871,8 +871,8 @@
|
|
|
871
871
|
"Place in dropdown": "放置在下拉列表中",
|
|
872
872
|
"Hide null columns": "隐藏空列",
|
|
873
873
|
"Do not display a column if it contains entirely missing values": "如果列包含完全缺失的值,则不显示",
|
|
874
|
-
"Show a warning to users creating a tenant disclaiming
|
|
875
|
-
"Set to 0 for
|
|
874
|
+
"Show a warning to users creating a tenant disclaiming warranty of availability or security": "向创建租户的用户显示警告,拒绝对可用性或安全性提供保证",
|
|
875
|
+
"Set to 0 for expiration at the end of browser session": "设置为 0 以在浏览器会话结束时进行扩展",
|
|
876
876
|
"Could not verify code": "无法验证代码",
|
|
877
877
|
"Disable two-factor authentication": "禁用双重身份验证",
|
|
878
878
|
"Enter your two-factor code in order to disable it": "输入您的双因素代码以禁用它",
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.7.2-beta.
|
|
3
|
+
"version": "0.7.2-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.7.2-beta.
|
|
10
|
-
"@saltcorn/builder": "0.7.2-beta.
|
|
11
|
-
"@saltcorn/data": "0.7.2-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.7.2-beta.
|
|
13
|
-
"@saltcorn/markup": "0.7.2-beta.
|
|
14
|
-
"@saltcorn/sbadmin2": "0.7.2-beta.
|
|
9
|
+
"@saltcorn/base-plugin": "0.7.2-beta.4",
|
|
10
|
+
"@saltcorn/builder": "0.7.2-beta.4",
|
|
11
|
+
"@saltcorn/data": "0.7.2-beta.4",
|
|
12
|
+
"@saltcorn/admin-models": "0.7.2-beta.4",
|
|
13
|
+
"@saltcorn/markup": "0.7.2-beta.4",
|
|
14
|
+
"@saltcorn/sbadmin2": "0.7.2-beta.4",
|
|
15
15
|
"@socket.io/cluster-adapter": "^0.1.0",
|
|
16
16
|
"@socket.io/sticky": "^1.0.1",
|
|
17
17
|
"aws-sdk": "^2.1037.0",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"greenlock-express": "^4.0.3",
|
|
33
33
|
"helmet": "^3.23.3",
|
|
34
34
|
"i18n": "^0.14.0",
|
|
35
|
+
"jsonwebtoken": "^8.5.1",
|
|
35
36
|
"live-plugin-manager": "^0.16.0",
|
|
36
37
|
"moment": "^2.27.0",
|
|
37
38
|
"multer": "^1.4.3",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"passport": "^0.4.1",
|
|
43
44
|
"passport-custom": "^1.1.1",
|
|
44
45
|
"passport-http-bearer": "^1.0.1",
|
|
46
|
+
"passport-jwt": "4.0.0",
|
|
45
47
|
"passport-totp": "0.0.2",
|
|
46
48
|
"pg": "^8.2.1",
|
|
47
49
|
"pluralize": "^8.0.0",
|
|
@@ -50,7 +52,8 @@
|
|
|
50
52
|
"socket.io": "4.2.0",
|
|
51
53
|
"thirty-two": "1.0.2",
|
|
52
54
|
"tmp-promise": "^3.0.2",
|
|
53
|
-
"uuid": "^8.2.0"
|
|
55
|
+
"uuid": "^8.2.0",
|
|
56
|
+
"cors": "2.8.5"
|
|
54
57
|
},
|
|
55
58
|
"optionalDependencies": {
|
|
56
59
|
"connect-sqlite3": "^0.9.11",
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
function press_store_button(clicked) {
|
|
2
|
+
const width = $(clicked).width();
|
|
3
|
+
$(clicked).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function notifyAlert(note, spin) {
|
|
7
|
+
if (Array.isArray(note)) {
|
|
8
|
+
note.forEach(notifyAlert);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
var txt, type;
|
|
12
|
+
if (typeof note == "string") {
|
|
13
|
+
txt = note;
|
|
14
|
+
type = "info";
|
|
15
|
+
} else {
|
|
16
|
+
txt = note.text;
|
|
17
|
+
type = note.type;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
$("#alerts-area")
|
|
21
|
+
.append(`<div class="alert alert-${type} alert-dismissible fade show ${
|
|
22
|
+
spin ? "d-flex align-items-center" : ""
|
|
23
|
+
}" role="alert">
|
|
24
|
+
${txt}
|
|
25
|
+
${
|
|
26
|
+
spin
|
|
27
|
+
? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
|
|
28
|
+
: `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
29
|
+
</button>`
|
|
30
|
+
}
|
|
31
|
+
</div>`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function common_done(res, isWeb = true) {
|
|
35
|
+
if (res.notify) notifyAlert(res.notify);
|
|
36
|
+
if (res.error) notifyAlert({ type: "danger", text: res.error });
|
|
37
|
+
if (res.eval_js) eval(res.eval_js);
|
|
38
|
+
if (res.reload_page) {
|
|
39
|
+
(isWeb ? location : parent.location).reload(); //TODO notify to cookie if reload or goto
|
|
40
|
+
}
|
|
41
|
+
if (res.download) {
|
|
42
|
+
const dataurl = `data:${
|
|
43
|
+
res.download.mimetype || "application/octet-stream"
|
|
44
|
+
};base64,${res.download.blob}`;
|
|
45
|
+
fetch(dataurl)
|
|
46
|
+
.then((res) => res.blob())
|
|
47
|
+
.then((blob) => {
|
|
48
|
+
const link = document.createElement("a");
|
|
49
|
+
link.href = window.URL.createObjectURL(blob);
|
|
50
|
+
if (res.download.filename) link.download = res.download.filename;
|
|
51
|
+
else link.target = "_blank";
|
|
52
|
+
link.click();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (res.goto && !isWeb)
|
|
56
|
+
notifyAlert({
|
|
57
|
+
type: "danger",
|
|
58
|
+
text: "Goto is not supported in a mobile deployment.",
|
|
59
|
+
});
|
|
60
|
+
else if (res.goto) {
|
|
61
|
+
if (res.target === "_blank") window.open(res.goto, "_blank").focus();
|
|
62
|
+
else window.location.href = res.goto;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function submitWithEmptyAction(form) {
|
|
67
|
+
var formAction = form.action;
|
|
68
|
+
form.action = "javascript:void(0)";
|
|
69
|
+
form.submit();
|
|
70
|
+
form.action = formAction;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function unique_field_from_rows(
|
|
74
|
+
rows,
|
|
75
|
+
id,
|
|
76
|
+
field_name,
|
|
77
|
+
space,
|
|
78
|
+
start,
|
|
79
|
+
always_append,
|
|
80
|
+
char_type,
|
|
81
|
+
value
|
|
82
|
+
) {
|
|
83
|
+
const gen_char = (i) => {
|
|
84
|
+
switch (char_type) {
|
|
85
|
+
case "Lowercase Letters":
|
|
86
|
+
return String.fromCharCode("a".charCodeAt(0) + i);
|
|
87
|
+
case "Uppercase Letters":
|
|
88
|
+
return String.fromCharCode("A".charCodeAt(0) + i);
|
|
89
|
+
default:
|
|
90
|
+
return i;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const vals = rows
|
|
94
|
+
.map((o) => o[field_name])
|
|
95
|
+
.filter((s) => s.startsWith(value));
|
|
96
|
+
if (vals.includes(value) || always_append) {
|
|
97
|
+
for (let i = start || 0; i < vals.length + (start || 0) + 2; i++) {
|
|
98
|
+
const newname = `${value}${space ? " " : ""}${gen_char(i)}`;
|
|
99
|
+
if (!vals.includes(newname)) {
|
|
100
|
+
$("#" + id).val(newname);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
package/public/saltcorn.css
CHANGED
|
@@ -233,3 +233,64 @@ footer.bs-mobile-nav-footer {
|
|
|
233
233
|
height: 100%;
|
|
234
234
|
z-index: -1;
|
|
235
235
|
}
|
|
236
|
+
|
|
237
|
+
input.hideupdownbtns::-webkit-outer-spin-button,
|
|
238
|
+
input.hideupdownbtns::-webkit-inner-spin-button {
|
|
239
|
+
-webkit-appearance: none;
|
|
240
|
+
margin: 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
input.hideupdownbtns[type="number"] {
|
|
244
|
+
-moz-appearance: textfield;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
section.range-slider {
|
|
248
|
+
position: relative;
|
|
249
|
+
width: 200px;
|
|
250
|
+
height: 35px;
|
|
251
|
+
text-align: center;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
section.range-slider input {
|
|
255
|
+
pointer-events: none;
|
|
256
|
+
position: absolute;
|
|
257
|
+
overflow: hidden;
|
|
258
|
+
left: 0;
|
|
259
|
+
top: 15px;
|
|
260
|
+
width: 200px;
|
|
261
|
+
outline: none;
|
|
262
|
+
height: 18px;
|
|
263
|
+
margin: 0;
|
|
264
|
+
padding: 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* https://stackoverflow.com/a/31083391 */
|
|
268
|
+
section.range-slider input::-webkit-slider-thumb {
|
|
269
|
+
pointer-events: all;
|
|
270
|
+
position: relative;
|
|
271
|
+
z-index: 1;
|
|
272
|
+
outline: 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
section.range-slider input::-moz-range-thumb {
|
|
276
|
+
pointer-events: all;
|
|
277
|
+
position: relative;
|
|
278
|
+
z-index: 10;
|
|
279
|
+
-moz-appearance: none;
|
|
280
|
+
width: 9px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
section.range-slider input::-moz-range-track {
|
|
284
|
+
position: relative;
|
|
285
|
+
z-index: -1;
|
|
286
|
+
background-color: rgba(0, 0, 0, 1);
|
|
287
|
+
border: 0;
|
|
288
|
+
}
|
|
289
|
+
section.range-slider input:last-of-type::-moz-range-track {
|
|
290
|
+
-moz-appearance: none;
|
|
291
|
+
background: none transparent;
|
|
292
|
+
border: 0;
|
|
293
|
+
}
|
|
294
|
+
section.range-slider input[type="range"]::-moz-focus-outer {
|
|
295
|
+
border: 0;
|
|
296
|
+
}
|
package/public/saltcorn.js
CHANGED
|
@@ -70,10 +70,12 @@ function apply_showif() {
|
|
|
70
70
|
|
|
71
71
|
var options = data[1][val];
|
|
72
72
|
var current = e.attr("data-selected");
|
|
73
|
-
//console.log(val, options, current,data)
|
|
73
|
+
//console.log({ val, options, current, data });
|
|
74
74
|
e.empty();
|
|
75
75
|
(options || []).forEach((o) => {
|
|
76
|
-
if (
|
|
76
|
+
if (
|
|
77
|
+
!(o && typeof o.label !== "undefined" && typeof o.value !== "undefined")
|
|
78
|
+
) {
|
|
77
79
|
if (`${current}` === `${o}`)
|
|
78
80
|
e.append($("<option selected>" + o + "</option>"));
|
|
79
81
|
else e.append($("<option>" + o + "</option>"));
|
|
@@ -219,6 +221,9 @@ if (localStorage.getItem("reload_on_init")) {
|
|
|
219
221
|
location.reload();
|
|
220
222
|
}
|
|
221
223
|
function initialize_page() {
|
|
224
|
+
$(".blur-on-enter-keypress").bind("keyup", function (e) {
|
|
225
|
+
if (e.keyCode === 13) e.target.blur();
|
|
226
|
+
});
|
|
222
227
|
$("form").change(apply_showif);
|
|
223
228
|
apply_showif();
|
|
224
229
|
apply_showif();
|
|
@@ -310,6 +315,34 @@ function initialize_page() {
|
|
|
310
315
|
});
|
|
311
316
|
$('a[data-bs-toggle="tab"].deeplink').historyTabs();
|
|
312
317
|
init_bs5_dropdowns();
|
|
318
|
+
|
|
319
|
+
// Initialize Sliders - https://stackoverflow.com/a/31083391
|
|
320
|
+
var sliderSections = document.getElementsByClassName("range-slider");
|
|
321
|
+
for (var x = 0; x < sliderSections.length; x++) {
|
|
322
|
+
var sliders = sliderSections[x].getElementsByTagName("input");
|
|
323
|
+
for (var y = 0; y < sliders.length; y++) {
|
|
324
|
+
if (sliders[y].type === "range") {
|
|
325
|
+
sliders[y].oninput = function () {
|
|
326
|
+
// Get slider values
|
|
327
|
+
var parent = this.parentNode;
|
|
328
|
+
var slides = parent.getElementsByTagName("input");
|
|
329
|
+
var slide1 = parseFloat(slides[0].value);
|
|
330
|
+
var slide2 = parseFloat(slides[1].value);
|
|
331
|
+
// Neither slider will clip the other, so make sure we determine which is larger
|
|
332
|
+
if (slide1 > slide2) {
|
|
333
|
+
var tmp = slide2;
|
|
334
|
+
slide2 = slide1;
|
|
335
|
+
slide1 = tmp;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
var displayElement = parent.getElementsByClassName("rangeValues")[0];
|
|
339
|
+
displayElement.innerHTML = slide1 + " - " + slide2;
|
|
340
|
+
};
|
|
341
|
+
// Manually trigger event first time to display values
|
|
342
|
+
sliders[y].oninput();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
313
346
|
}
|
|
314
347
|
|
|
315
348
|
$(initialize_page);
|
|
@@ -459,57 +492,8 @@ function tristateClick(nm) {
|
|
|
459
492
|
}
|
|
460
493
|
}
|
|
461
494
|
|
|
462
|
-
function notifyAlert(note, spin) {
|
|
463
|
-
if (Array.isArray(note)) {
|
|
464
|
-
note.forEach(notifyAlert);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
var txt, type;
|
|
468
|
-
if (typeof note == "string") {
|
|
469
|
-
txt = note;
|
|
470
|
-
type = "info";
|
|
471
|
-
} else {
|
|
472
|
-
txt = note.text;
|
|
473
|
-
type = note.type;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
$("#alerts-area")
|
|
477
|
-
.append(`<div class="alert alert-${type} alert-dismissible fade show ${
|
|
478
|
-
spin ? "d-flex align-items-center" : ""
|
|
479
|
-
}" role="alert">
|
|
480
|
-
${txt}
|
|
481
|
-
${
|
|
482
|
-
spin
|
|
483
|
-
? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
|
|
484
|
-
: `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
485
|
-
</button>`
|
|
486
|
-
}
|
|
487
|
-
</div>`);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
495
|
function ajax_done(res) {
|
|
491
|
-
|
|
492
|
-
if (res.error) notifyAlert({ type: "danger", text: res.error });
|
|
493
|
-
if (res.eval_js) eval(res.eval_js);
|
|
494
|
-
if (res.reload_page) location.reload(); //TODO notify to cookie if reload or goto
|
|
495
|
-
if (res.download) {
|
|
496
|
-
const dataurl = `data:${
|
|
497
|
-
res.download.mimetype || "application/octet-stream"
|
|
498
|
-
};base64,${res.download.blob}`;
|
|
499
|
-
fetch(dataurl)
|
|
500
|
-
.then((res) => res.blob())
|
|
501
|
-
.then((blob) => {
|
|
502
|
-
const link = document.createElement("a");
|
|
503
|
-
link.href = window.URL.createObjectURL(blob);
|
|
504
|
-
if (res.download.filename) link.download = res.download.filename;
|
|
505
|
-
else link.target = "_blank";
|
|
506
|
-
link.click();
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
if (res.goto) {
|
|
510
|
-
if (res.target === "_blank") window.open(res.goto, "_blank").focus();
|
|
511
|
-
else window.location.href = res.goto;
|
|
512
|
-
}
|
|
496
|
+
common_done(res);
|
|
513
497
|
}
|
|
514
498
|
|
|
515
499
|
function view_post(viewname, route, data, onDone) {
|
|
@@ -546,11 +530,6 @@ function globalErrorCatcher(message, source, lineno, colno, error) {
|
|
|
546
530
|
});
|
|
547
531
|
}
|
|
548
532
|
|
|
549
|
-
function press_store_button(clicked) {
|
|
550
|
-
const width = $(clicked).width();
|
|
551
|
-
$(clicked).html('<i class="fas fa-spinner fa-spin"></i>').width(width);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
533
|
function ajax_modal(url, opts = {}) {
|
|
555
534
|
if ($("#scmodal").length === 0) {
|
|
556
535
|
$("body").append(`<div id="scmodal", class="modal">
|
|
@@ -591,6 +570,7 @@ function ajax_modal(url, opts = {}) {
|
|
|
591
570
|
|
|
592
571
|
function saveAndContinue(e, k) {
|
|
593
572
|
var form = $(e).closest("form");
|
|
573
|
+
submitWithEmptyAction(form[0]);
|
|
594
574
|
var url = form.attr("action");
|
|
595
575
|
var form_data = form.serialize();
|
|
596
576
|
$.ajax(url, {
|
|
@@ -641,7 +621,7 @@ function applyViewConfig(e, url) {
|
|
|
641
621
|
|
|
642
622
|
const repeaterCopyValuesToForm = (form, editor) => {
|
|
643
623
|
const vs = JSON.parse(editor.getString());
|
|
644
|
-
|
|
624
|
+
const allNames = new Set([]);
|
|
645
625
|
const setVal = (k, ix, v) => {
|
|
646
626
|
const $e = form.find(`input[name="${k}_${ix}"]`);
|
|
647
627
|
if ($e.length) $e.val(v);
|
|
@@ -653,10 +633,17 @@ const repeaterCopyValuesToForm = (form, editor) => {
|
|
|
653
633
|
vs.forEach((v, ix) => {
|
|
654
634
|
Object.entries(v).forEach(([k, v]) => {
|
|
655
635
|
//console.log(ix, k, typeof v, v)
|
|
636
|
+
allNames.add(k);
|
|
656
637
|
if (typeof v === "boolean") setVal(k, ix, v ? "on" : "");
|
|
657
638
|
else setVal(k, ix, v);
|
|
658
639
|
});
|
|
659
640
|
});
|
|
641
|
+
//delete
|
|
642
|
+
[...allNames].forEach((k) => {
|
|
643
|
+
for (let ix = vs.length; ix < vs.length + 20; ix++) {
|
|
644
|
+
$(`input[name=${k}_${ix}]`).remove();
|
|
645
|
+
}
|
|
646
|
+
});
|
|
660
647
|
};
|
|
661
648
|
|
|
662
649
|
function ajaxSubmitForm(e) {
|
|
@@ -743,31 +730,16 @@ function make_unique_field(
|
|
|
743
730
|
type: "GET",
|
|
744
731
|
success: function (res) {
|
|
745
732
|
if (res.success) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
break;
|
|
757
|
-
}
|
|
758
|
-
};
|
|
759
|
-
const vals = res.success
|
|
760
|
-
.map((o) => o[field_name])
|
|
761
|
-
.filter((s) => s.startsWith(value));
|
|
762
|
-
if (vals.includes(value) || always_append) {
|
|
763
|
-
for (let i = start || 0; i < vals.length + (start || 0) + 2; i++) {
|
|
764
|
-
const newname = `${value}${space ? " " : ""}${gen_char(i)}`;
|
|
765
|
-
if (!vals.includes(newname)) {
|
|
766
|
-
$("#" + id).val(newname);
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
733
|
+
unique_field_from_rows(
|
|
734
|
+
res.success,
|
|
735
|
+
id,
|
|
736
|
+
field_name,
|
|
737
|
+
space,
|
|
738
|
+
start,
|
|
739
|
+
always_append,
|
|
740
|
+
char_type,
|
|
741
|
+
value
|
|
742
|
+
);
|
|
771
743
|
}
|
|
772
744
|
},
|
|
773
745
|
}
|
package/routes/admin.js
CHANGED
|
@@ -787,7 +787,7 @@ router.get(
|
|
|
787
787
|
? div(
|
|
788
788
|
{ class: "alert alert-success", role: "alert" },
|
|
789
789
|
i({ class: "fas fa-check-circle fa-lg me-2" }),
|
|
790
|
-
h5({ class: "d-inline" }, "No errors detected")
|
|
790
|
+
h5({ class: "d-inline" }, req.__("No errors detected during configuration check"))
|
|
791
791
|
)
|
|
792
792
|
: errors.map(mkError)
|
|
793
793
|
),
|
package/routes/api.js
CHANGED
|
@@ -20,6 +20,7 @@ const { error_catcher } = require("./utils.js");
|
|
|
20
20
|
//const { mkTable, renderForm, link, post_btn } = require("@saltcorn/markup");
|
|
21
21
|
const { getState } = require("@saltcorn/data/db/state");
|
|
22
22
|
const Table = require("@saltcorn/data/models/table");
|
|
23
|
+
const View = require("@saltcorn/data/models/view");
|
|
23
24
|
//const Field = require("@saltcorn/data/models/field");
|
|
24
25
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
25
26
|
//const load_plugins = require("../load_plugins");
|
|
@@ -111,6 +112,40 @@ function accessAllowed(req, user, trigger) {
|
|
|
111
112
|
return role <= trigger.min_role;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
router.post(
|
|
116
|
+
"/viewQuery/:viewName/:queryName",
|
|
117
|
+
error_catcher(async (req, res, next) => {
|
|
118
|
+
let { viewName, queryName } = req.params;
|
|
119
|
+
const view = await View.findOne({ name: viewName });
|
|
120
|
+
if (!view) {
|
|
121
|
+
res.status(404).json({ error: req.__("Not found") });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
await passport.authenticate(
|
|
125
|
+
"jwt",
|
|
126
|
+
{ session: false },
|
|
127
|
+
async function (err, user, info) {
|
|
128
|
+
const role = user && user.id ? user.role_id : 10;
|
|
129
|
+
if (
|
|
130
|
+
role <= view.min_role ||
|
|
131
|
+
(await view.authorise_get({ req, ...view })) // TODO set query to state
|
|
132
|
+
) {
|
|
133
|
+
const queries = view.queries(false, req);
|
|
134
|
+
if (queries[queryName]) {
|
|
135
|
+
const { args } = req.body;
|
|
136
|
+
const resp = await queries[queryName](...args, true);
|
|
137
|
+
res.json({ success: resp });
|
|
138
|
+
} else {
|
|
139
|
+
res.status(404).json({ error: req.__("Not found") });
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
res.status(401).json({ error: req.__("Not authorized") });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
)(req, res, next);
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
|
|
114
149
|
router.get(
|
|
115
150
|
"/:tableName/distinct/:fieldName",
|
|
116
151
|
//passport.authenticate("api-bearer", { session: false }),
|
|
@@ -180,7 +215,7 @@ router.get(
|
|
|
180
215
|
}
|
|
181
216
|
|
|
182
217
|
await passport.authenticate(
|
|
183
|
-
"api-bearer",
|
|
218
|
+
["api-bearer", "jwt"],
|
|
184
219
|
{ session: false },
|
|
185
220
|
async function (err, user, info) {
|
|
186
221
|
if (accessAllowedRead(req, user, table)) {
|
package/routes/edit.js
CHANGED
|
@@ -44,7 +44,8 @@ router.post(
|
|
|
44
44
|
"error",
|
|
45
45
|
req.__("Not allowed to write to table %s", table.name)
|
|
46
46
|
);
|
|
47
|
-
if (req.
|
|
47
|
+
if (req.xhr) res.send("OK");
|
|
48
|
+
else if (req.get("referer")) res.redirect(req.get("referer"));
|
|
48
49
|
else res.redirect(redirect || `/list/${table.name}`);
|
|
49
50
|
})
|
|
50
51
|
);
|