@saltcorn/server 0.7.4-beta.2 → 0.7.4-beta.3
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/locales/en.json +21 -4
- package/locales/ru.json +60 -7
- package/markup/admin.js +3 -4
- package/markup/expression_blurb.js +1 -1
- package/package.json +7 -7
- package/public/blockly.js +11 -5
- package/public/gridedit.js +7 -2
- package/public/saltcorn-common.js +27 -29
- package/public/saltcorn.js +47 -12
- package/routes/admin.js +16 -17
- package/routes/api.js +7 -6
- package/routes/diagram.js +59 -0
- package/routes/fields.js +20 -7
- package/routes/index.js +2 -0
- package/routes/pageedit.js +2 -3
- package/routes/plugins.js +15 -15
- package/routes/tables.js +6 -2
- package/routes/tag_entries.js +2 -2
- package/routes/tags.js +4 -4
- package/tests/page.test.js +9 -0
- package/tests/viewedit.test.js +16 -0
package/locales/en.json
CHANGED
|
@@ -951,7 +951,6 @@
|
|
|
951
951
|
"Modules up-to-date": "Modules up-to-date",
|
|
952
952
|
"User must have this role or higher to read rows from the table, unless they are the owner": "User must have this role or higher to read rows from the table, unless they are the owner",
|
|
953
953
|
"User must have this role or higher to edit or create new rows in the table, unless they are the owner": "User must have this role or higher to edit or create new rows in the table, unless they are the owner",
|
|
954
|
-
"Tag": "Tag",
|
|
955
954
|
"Tagname": "Tagname",
|
|
956
955
|
"Create tag": "Create tag",
|
|
957
956
|
"Tags": "Tags",
|
|
@@ -965,6 +964,24 @@
|
|
|
965
964
|
"Trigger": "Trigger",
|
|
966
965
|
"Add %s to tag": "Add %s to tag",
|
|
967
966
|
"Tag %s deleted": "Tag %s deleted",
|
|
968
|
-
"Tag %s created": "Tag %s created"
|
|
969
|
-
|
|
970
|
-
|
|
967
|
+
"Tag %s created": "Tag %s created",
|
|
968
|
+
"Application diagram": "Application diagram",
|
|
969
|
+
"Diagram": "Diagram",
|
|
970
|
+
"Entry point": "Entry point",
|
|
971
|
+
"Platform": "Platform",
|
|
972
|
+
"docker": "docker",
|
|
973
|
+
"android": "android",
|
|
974
|
+
"iOS": "iOS",
|
|
975
|
+
"App file": "App file",
|
|
976
|
+
"Server URL": "Server URL",
|
|
977
|
+
"Module %s installed, please complete configuration.": "Module %s installed, please complete configuration.",
|
|
978
|
+
"Module %s removed.": "Module %s removed.",
|
|
979
|
+
"Module %s installed": "Module %s installed",
|
|
980
|
+
"Upgrading modules...": "Upgrading modules...",
|
|
981
|
+
"Backup now": "Backup now",
|
|
982
|
+
"Snapshot now": "Snapshot now",
|
|
983
|
+
"Restore/download automated backups »": "Restore/download automated backups »",
|
|
984
|
+
"Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).": "Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).",
|
|
985
|
+
"List/download snapshots »": "List/download snapshots »",
|
|
986
|
+
"Discover tables that are already in the Database, but not known to Saltcorn": "Discover tables that are already in the Database, but not known to Saltcorn"
|
|
987
|
+
}
|
package/locales/ru.json
CHANGED
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
"Pack %s uninstalled": "Пакет %s удален",
|
|
181
181
|
"Uninstall": "Удалить",
|
|
182
182
|
"Result preview for ": "Предварительный просмотр результатов для ",
|
|
183
|
-
"Choose views for <a href=\"/search\">search results</a> for each table.<br/>Set to blank to omit table from global search.": "Выберите для каждой таблицы
|
|
183
|
+
"Choose views for <a href=\"/search\">search results</a> for each table.<br/>Set to blank to omit table from global search.": "Выберите для каждой таблицы представление для вывода <a href=\"/search\">результатов поиска</a>.<br/>Оставьте незаполненным, если не хотите, чтобы таблица отображалась в глобальном поиске.",
|
|
184
184
|
"These tables lack suitable views: ": "Указанные таблицы не имеют представлений с поддержкой поиска: ",
|
|
185
185
|
"Search configuration": "Настройки поиска",
|
|
186
186
|
"Table saved with version history enabled": "Таблица сохранена с включенной историей версий",
|
|
@@ -303,7 +303,7 @@
|
|
|
303
303
|
"Clone": "Клонировать",
|
|
304
304
|
"View %s cloned as %s": "Представление %s клонировано в %s",
|
|
305
305
|
"Duplicate": "Дублировать",
|
|
306
|
-
"View %s duplicated as %s": "Представление %s
|
|
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
308
|
"Saltcorn version": "версия платформы",
|
|
309
309
|
"Node.js version": "версия Node.js",
|
|
@@ -337,7 +337,7 @@
|
|
|
337
337
|
"Subtables": "Подчиненные таблицы",
|
|
338
338
|
"List View": "Представление Список (List)",
|
|
339
339
|
"Show View": "Представление Show",
|
|
340
|
-
"Which related tables would you like to show in sub-lists below the selected item?": "
|
|
340
|
+
"Which related tables would you like to show in sub-lists below the selected item?": "Какие связанные таблицы вы хотите отобразить в подчиненных списках (sub-lists) под выбранной записью?",
|
|
341
341
|
"Order and layout": "Сортировка и Макет",
|
|
342
342
|
"Order by": "Сортировка по",
|
|
343
343
|
"Descending": "В обратном порядке",
|
|
@@ -801,11 +801,11 @@
|
|
|
801
801
|
"Expiration in days": "Срок хранения (в днях)",
|
|
802
802
|
"Delete old backup files in this directory after the set number of days": "Старые резервные копии в этой папке будут удалять после истечения срока хранения ",
|
|
803
803
|
"Backup settings updated": "Настройки резервного копирования обновлены",
|
|
804
|
-
"Periodic snapshots enabled": "Регулярные снимки (
|
|
805
|
-
"Snapshot will be made every hour if there are changes": "Снимки (
|
|
804
|
+
"Periodic snapshots enabled": "Регулярные снимки (снепшоты)",
|
|
805
|
+
"Snapshot will be made every hour if there are changes": "Снимки (снепшоты) будут выполняться каждый час",
|
|
806
806
|
"Manual backup": "Ручное резервное копирование",
|
|
807
807
|
"Automated backup": "Автоматическое резервное копирование",
|
|
808
|
-
"Snapshots": "Снимки (
|
|
808
|
+
"Snapshots": "Снимки (снепшоты)",
|
|
809
809
|
"Mobile app": "Мобильное приложение",
|
|
810
810
|
"Module store": "Магазин модулей",
|
|
811
811
|
"Upgrade installed modules": "Обновить установленные модули",
|
|
@@ -829,5 +829,58 @@
|
|
|
829
829
|
"App file": "Файл приложения",
|
|
830
830
|
"Server URL": "URL сервера",
|
|
831
831
|
"android": "android",
|
|
832
|
-
"iOS": "iOS"
|
|
832
|
+
"iOS": "iOS",
|
|
833
|
+
"Tag %s created": "Тег %s создан",
|
|
834
|
+
"%s Tag": "%s Тег",
|
|
835
|
+
"Add tables": "Добавить таблицы",
|
|
836
|
+
"Add views": "Добавить представления",
|
|
837
|
+
"Remove From Tag": "Удалить из тега",
|
|
838
|
+
"Add tages": "Добавить теги",
|
|
839
|
+
"Trigger": "Триггер",
|
|
840
|
+
"Tag": "Тег",
|
|
841
|
+
"Download snapshots": "Скачать снепшоты",
|
|
842
|
+
"Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).": "Снепшоты сохраняют структуру приложения, но не сохраняют данные в таблицах. Конкретные представления и страницы могут быть восстановлены по ссылке <a href='/viewedit'>view</a> или <a href='/pageedit'>pages</a> (Кнопка \"Восстановить\" в выпадающем меню представления или страницы).",
|
|
843
|
+
"List/download snapshots »": "Список снепшотов »",
|
|
844
|
+
"Add %s to tag": "Добавить %s к тегу",
|
|
845
|
+
"Tagname": "Имя тега",
|
|
846
|
+
"Create tag": "Создать тег",
|
|
847
|
+
"Tags": "Теги",
|
|
848
|
+
"New tag": "Новый тег",
|
|
849
|
+
"Tag name": "Имя тега",
|
|
850
|
+
"Tag %s deleted": "Тег %s удален",
|
|
851
|
+
"Add entries to tag": "Добавить объекты в тег",
|
|
852
|
+
"Please select at least one item": "Пожалуйста добавьте хотя бы один объект",
|
|
853
|
+
"Backup now": "Сделать бекап сейчас",
|
|
854
|
+
"Snapshot now": "Сделать снепшот сейчас",
|
|
855
|
+
"Restore/download automated backups »": "Восстановить/скачать резервные копии »",
|
|
856
|
+
"No changes detected, snapshot skipped": "Нет изменений, снепшот пропущен",
|
|
857
|
+
"Pattern": "Паттерн",
|
|
858
|
+
"Add pages": "Добавить страницы",
|
|
859
|
+
"Add triggers": "Добавить триггеры",
|
|
860
|
+
"Edit Module": "Настроить Модуль",
|
|
861
|
+
"Page %s duplicated as %s": "Страница %s дублирована в %s",
|
|
862
|
+
"Auto save": "Автосохранение",
|
|
863
|
+
"Save any changes immediately": "Сохранять все изменения данных в форме немедленно",
|
|
864
|
+
"This is the view to which the user will be sent when the form is submitted. The view you specify here can be ignored depending on the context of the form, for instance if it appears in a pop-up the redirect will not take place.": "Указывается представление, куда будет перенаправлен пользователь после отправки формы. В некоторых случаях перенаправление не будет выполнено, например, если форма использована в режиме всплывающего окна.",
|
|
865
|
+
"Destination view": "Destination view",
|
|
866
|
+
"Destination URL Formula": "Destination URL Formula",
|
|
867
|
+
"Back": "Назад",
|
|
868
|
+
"Backup successful": "Резервная копия успешно создана",
|
|
869
|
+
"Save before going back": "Save before going back",
|
|
870
|
+
"Reload after going back": "Reload after going back",
|
|
871
|
+
"Steps to go back": "Steps to go back",
|
|
872
|
+
"View pattern": "Паттерн представления",
|
|
873
|
+
"The view pattern sets the foundation of how the view relates to the table and the behaviour of the view": "The view pattern sets the foundation of how the view relates to the table and the behaviour of the view",
|
|
874
|
+
"This will delete <strong>EVERYTHING</strong> in the selected categories": "Внимание! Будут удалены <strong>ВСЕ</strong> данные в выбранных категориях",
|
|
875
|
+
"Tag not found": "Тэг не найден",
|
|
876
|
+
"Build Result": "Результат сборки",
|
|
877
|
+
"Download automated backup": "Автоматически созданные резервные копии",
|
|
878
|
+
"Restoring automated backup": "Восстановление из автоматически созданной резервной копии",
|
|
879
|
+
"Download one of the backups above": "Скачать один из бекапов (файлов с резервной копией) выше",
|
|
880
|
+
"Clear this application": "Удалить ВСЕ данные в текущем приложении",
|
|
881
|
+
"(tick all boxes)": "(указав все галочки)",
|
|
882
|
+
"When prompted to create the first user, click the link to restore a backup": "Вместо создания первого пользователя, выберите Восстановление из резервной копии (restore a backup)",
|
|
883
|
+
"Select the downloaded backup file": "Выбрать скаченный бекап (файл с резервной копией)",
|
|
884
|
+
"Snapshot successful": "Снимок (Снепшот) выполнен успешно",
|
|
885
|
+
"Unable to build the app:": "Ошибка создания приложения:"
|
|
833
886
|
}
|
package/markup/admin.js
CHANGED
|
@@ -203,6 +203,8 @@ const send_infoarch_page = (args) => {
|
|
|
203
203
|
{ text: "Multitenancy", href: "/tenant/settings" },
|
|
204
204
|
]
|
|
205
205
|
: []),
|
|
206
|
+
{ text: "Tags", href: "/tag" },
|
|
207
|
+
{ text: "Diagram", href: "/diagram" },
|
|
206
208
|
],
|
|
207
209
|
...args,
|
|
208
210
|
});
|
|
@@ -288,10 +290,7 @@ const send_admin_page = (args) => {
|
|
|
288
290
|
{ text: "Backup", href: "/admin/backup" },
|
|
289
291
|
{ text: "Email", href: "/admin/email" },
|
|
290
292
|
{ text: "System", href: "/admin/system" },
|
|
291
|
-
|
|
292
|
-
...(isRoot
|
|
293
|
-
? [{ text: "Mobile app", href: "/admin/build-mobile-app" }]
|
|
294
|
-
: []),
|
|
293
|
+
{ text: "Mobile app", href: "/admin/build-mobile-app" },
|
|
295
294
|
],
|
|
296
295
|
...args,
|
|
297
296
|
});
|
|
@@ -146,7 +146,7 @@ const boolExamples = (type, fields) => {
|
|
|
146
146
|
* @returns {p[]}
|
|
147
147
|
*/
|
|
148
148
|
const expressionBlurb = (type, stored, allFields, req) => {
|
|
149
|
-
const fields = allFields.filter((f) => !f.
|
|
149
|
+
const fields = allFields.filter((f) => !f.calculated);
|
|
150
150
|
const funs = getState().functions;
|
|
151
151
|
const funNames = Object.entries(funs)
|
|
152
152
|
.filter(([k, v]) => !(!stored && v.isAsync))
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.7.4-beta.
|
|
3
|
+
"version": "0.7.4-beta.3",
|
|
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.4-beta.
|
|
10
|
-
"@saltcorn/builder": "0.7.4-beta.
|
|
11
|
-
"@saltcorn/data": "0.7.4-beta.
|
|
12
|
-
"@saltcorn/admin-models": "0.7.4-beta.
|
|
13
|
-
"@saltcorn/markup": "0.7.4-beta.
|
|
14
|
-
"@saltcorn/sbadmin2": "0.7.4-beta.
|
|
9
|
+
"@saltcorn/base-plugin": "0.7.4-beta.3",
|
|
10
|
+
"@saltcorn/builder": "0.7.4-beta.3",
|
|
11
|
+
"@saltcorn/data": "0.7.4-beta.3",
|
|
12
|
+
"@saltcorn/admin-models": "0.7.4-beta.3",
|
|
13
|
+
"@saltcorn/markup": "0.7.4-beta.3",
|
|
14
|
+
"@saltcorn/sbadmin2": "0.7.4-beta.3",
|
|
15
15
|
"@socket.io/cluster-adapter": "^0.1.0",
|
|
16
16
|
"@socket.io/sticky": "^1.0.1",
|
|
17
17
|
"aws-sdk": "^2.1037.0",
|
package/public/blockly.js
CHANGED
|
@@ -403,11 +403,10 @@ function activate_blockly({ events, actions, tables }) {
|
|
|
403
403
|
Blockly.JavaScript.ORDER_ATOMIC
|
|
404
404
|
);
|
|
405
405
|
// TODO: Assemble JavaScript into code variable.
|
|
406
|
-
var code = `await fetchJSON(${value_url}, { method: '${dropdown_method}'${
|
|
407
|
-
value_body
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
} })`;
|
|
406
|
+
var code = `await fetchJSON(${value_url}, { method: '${dropdown_method}'${value_body
|
|
407
|
+
? `, body: ${value_body}, headers: { "Content-Type": "application/json" }`
|
|
408
|
+
: ""
|
|
409
|
+
} })`;
|
|
411
410
|
// TODO: Change ORDER_NONE to the correct strength.
|
|
412
411
|
return [code, Blockly.JavaScript.ORDER_NONE];
|
|
413
412
|
};
|
|
@@ -416,6 +415,7 @@ function activate_blockly({ events, actions, tables }) {
|
|
|
416
415
|
init: function () {
|
|
417
416
|
this.appendDummyInput().appendField("Return");
|
|
418
417
|
this.appendValueInput("GOTO").setCheck("String").appendField("Go to URL");
|
|
418
|
+
this.appendValueInput("POPUP").setCheck("String").appendField("Popup URL");
|
|
419
419
|
this.appendDummyInput()
|
|
420
420
|
.appendField("Reload page")
|
|
421
421
|
.appendField(new Blockly.FieldCheckbox("FALSE"), "RELOAD");
|
|
@@ -434,6 +434,11 @@ function activate_blockly({ events, actions, tables }) {
|
|
|
434
434
|
"GOTO",
|
|
435
435
|
Blockly.JavaScript.ORDER_ATOMIC
|
|
436
436
|
);
|
|
437
|
+
var value_popup = Blockly.JavaScript.valueToCode(
|
|
438
|
+
block,
|
|
439
|
+
"POPUP",
|
|
440
|
+
Blockly.JavaScript.ORDER_ATOMIC
|
|
441
|
+
);
|
|
437
442
|
var checkbox_reload = block.getFieldValue("RELOAD") == "TRUE";
|
|
438
443
|
var value_notify = Blockly.JavaScript.valueToCode(
|
|
439
444
|
block,
|
|
@@ -443,6 +448,7 @@ function activate_blockly({ events, actions, tables }) {
|
|
|
443
448
|
// TODO: Assemble JavaScript into code variable.
|
|
444
449
|
let s = "";
|
|
445
450
|
if (value_goto) s += `goto: ${value_goto},`;
|
|
451
|
+
if (value_popup) s += `popup: ${value_popup},`;
|
|
446
452
|
if (value_notify) s += `notify: ${value_notify},`;
|
|
447
453
|
if (checkbox_reload) s += `reload_page: true,`;
|
|
448
454
|
var code = `return {${s}};\n`;
|
package/public/gridedit.js
CHANGED
|
@@ -4,7 +4,10 @@ function showHideCol(nm, e) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
function lookupIntToString(cell, formatterParams, onRendered) {
|
|
7
|
-
const
|
|
7
|
+
const cellVal = cell.getValue()
|
|
8
|
+
const val = typeof cellVal === "object" && cellVal !== null
|
|
9
|
+
? `${cellVal.id}`
|
|
10
|
+
: `${cellVal}`;
|
|
8
11
|
const res = formatterParams.values[val];
|
|
9
12
|
return res;
|
|
10
13
|
}
|
|
@@ -150,6 +153,8 @@ function delete_tabulator_row(e, cell) {
|
|
|
150
153
|
if (def && def.formatterParams && def.formatterParams.confirm) {
|
|
151
154
|
if (!confirm("Are you sure you want to delete this row?")) return;
|
|
152
155
|
}
|
|
156
|
+
const tableName = def?.formatterParams?.tableName || window.tabulator_table_name
|
|
157
|
+
|
|
153
158
|
const row = cell.getRow().getData();
|
|
154
159
|
if (!row.id) {
|
|
155
160
|
cell.getRow().delete();
|
|
@@ -157,7 +162,7 @@ function delete_tabulator_row(e, cell) {
|
|
|
157
162
|
}
|
|
158
163
|
$.ajax({
|
|
159
164
|
type: "DELETE",
|
|
160
|
-
url: `/api/${
|
|
165
|
+
url: `/api/${tableName}/${row.id}`,
|
|
161
166
|
data: row, // to process primary keys different from id
|
|
162
167
|
headers: {
|
|
163
168
|
"CSRF-Token": _sc_globalCsrf,
|
|
@@ -86,8 +86,7 @@ function apply_showif() {
|
|
|
86
86
|
} else {
|
|
87
87
|
e.append(
|
|
88
88
|
$(
|
|
89
|
-
`<option ${
|
|
90
|
-
`${current}` === `${o.value}` ? "selected" : ""
|
|
89
|
+
`<option ${`${current}` === `${o.value}` ? "selected" : ""
|
|
91
90
|
} value="${o.value}">${o.label}</option>`
|
|
92
91
|
)
|
|
93
92
|
);
|
|
@@ -118,15 +117,13 @@ function apply_showif() {
|
|
|
118
117
|
resp.success.forEach((r) => {
|
|
119
118
|
e.append(
|
|
120
119
|
$(
|
|
121
|
-
`<option ${
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)(r)
|
|
129
|
-
: r[dynwhere.summary_field]
|
|
120
|
+
`<option ${`${current}` === `${r[dynwhere.refname]}` ? "selected" : ""
|
|
121
|
+
} value="${r[dynwhere.refname]}">${dynwhere.label_formula
|
|
122
|
+
? new Function(
|
|
123
|
+
`{${Object.keys(r).join(",")}}`,
|
|
124
|
+
"return " + dynwhere.label_formula
|
|
125
|
+
)(r)
|
|
126
|
+
: r[dynwhere.summary_field]
|
|
130
127
|
}</option>`
|
|
131
128
|
)
|
|
132
129
|
);
|
|
@@ -466,16 +463,14 @@ function notifyAlert(note, spin) {
|
|
|
466
463
|
}
|
|
467
464
|
|
|
468
465
|
$("#alerts-area")
|
|
469
|
-
.append(`<div class="alert alert-${type} alert-dismissible fade show ${
|
|
470
|
-
|
|
471
|
-
}" role="alert">
|
|
466
|
+
.append(`<div class="alert alert-${type} alert-dismissible fade show ${spin ? "d-flex align-items-center" : ""
|
|
467
|
+
}" role="alert">
|
|
472
468
|
${txt}
|
|
473
|
-
${
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
: `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
469
|
+
${spin
|
|
470
|
+
? `<div class="spinner-border ms-auto" role="status" aria-hidden="true"></div>`
|
|
471
|
+
: `<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
477
472
|
</button>`
|
|
478
|
-
|
|
473
|
+
}
|
|
479
474
|
</div>`);
|
|
480
475
|
}
|
|
481
476
|
|
|
@@ -492,9 +487,8 @@ function common_done(res, isWeb = true) {
|
|
|
492
487
|
(isWeb ? location : parent.location).reload(); //TODO notify to cookie if reload or goto
|
|
493
488
|
}
|
|
494
489
|
if (res.download) {
|
|
495
|
-
const dataurl = `data:${
|
|
496
|
-
res.download.
|
|
497
|
-
};base64,${res.download.blob}`;
|
|
490
|
+
const dataurl = `data:${res.download.mimetype || "application/octet-stream"
|
|
491
|
+
};base64,${res.download.blob}`;
|
|
498
492
|
fetch(dataurl)
|
|
499
493
|
.then((res) => res.blob())
|
|
500
494
|
.then((blob) => {
|
|
@@ -515,6 +509,9 @@ function common_done(res, isWeb = true) {
|
|
|
515
509
|
if (res.target === "_blank") window.open(res.goto, "_blank").focus();
|
|
516
510
|
else window.location.href = res.goto;
|
|
517
511
|
}
|
|
512
|
+
if (res.popup) {
|
|
513
|
+
ajax_modal(res.popup)
|
|
514
|
+
}
|
|
518
515
|
}
|
|
519
516
|
|
|
520
517
|
const repeaterCopyValuesToForm = (form, editor, noTriggerChange) => {
|
|
@@ -523,10 +520,11 @@ const repeaterCopyValuesToForm = (form, editor, noTriggerChange) => {
|
|
|
523
520
|
const setVal = (k, ix, v) => {
|
|
524
521
|
const $e = form.find(`input[name="${k}_${ix}"]`);
|
|
525
522
|
if ($e.length) $e.val(v);
|
|
526
|
-
else
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
);
|
|
523
|
+
else {
|
|
524
|
+
const $ne = $(`<input type="hidden" data-repeater-ix="${ix}" name="${k}_${ix}"></input>`);
|
|
525
|
+
$ne.val(v);
|
|
526
|
+
form.append($ne);
|
|
527
|
+
}
|
|
530
528
|
};
|
|
531
529
|
vs.forEach((v, ix) => {
|
|
532
530
|
Object.entries(v).forEach(([k, v]) => {
|
|
@@ -649,9 +647,9 @@ function room_older(viewname, room_id, btn) {
|
|
|
649
647
|
function init_room(viewname, room_id) {
|
|
650
648
|
const socket = parent?.config?.server_path
|
|
651
649
|
? io(parent.config.server_path, {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
650
|
+
query: `jwt=${localStorage.getItem("auth_jwt")}`,
|
|
651
|
+
transports: ["websocket"],
|
|
652
|
+
})
|
|
655
653
|
: io({ transports: ["websocket"] });
|
|
656
654
|
|
|
657
655
|
socket.emit("join_room", [viewname, room_id]);
|
package/public/saltcorn.js
CHANGED
|
@@ -51,12 +51,19 @@ function removeQueryStringParameter(uri1, key) {
|
|
|
51
51
|
return uri + hash;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function get_current_state_url() {
|
|
55
|
+
let $modal = $("#scmodal");
|
|
56
|
+
if ($modal.length === 0 || !$modal.hasClass("show"))
|
|
57
|
+
return window.location.href;
|
|
58
|
+
else return $modal.prop("data-modal-state");
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
function select_id(id) {
|
|
55
|
-
pjax_to(updateQueryStringParameter(
|
|
62
|
+
pjax_to(updateQueryStringParameter(get_current_state_url(), "id", id));
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
function set_state_field(key, value) {
|
|
59
|
-
pjax_to(updateQueryStringParameter(
|
|
66
|
+
pjax_to(updateQueryStringParameter(get_current_state_url(), key, value));
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
function check_state_field(that) {
|
|
@@ -65,13 +72,13 @@ function check_state_field(that) {
|
|
|
65
72
|
const value = that.value;
|
|
66
73
|
var separator = window.location.href.indexOf("?") !== -1 ? "&" : "?";
|
|
67
74
|
let dest;
|
|
68
|
-
if (checked) dest =
|
|
69
|
-
else dest =
|
|
75
|
+
if (checked) dest = get_current_state_url() + `${separator}${name}=${value}`;
|
|
76
|
+
else dest = get_current_state_url().replace(`${name}=${value}`, "");
|
|
70
77
|
pjax_to(dest.replace("&&", "&").replace("?&", "?"));
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
function set_state_fields(kvs) {
|
|
74
|
-
var newhref =
|
|
81
|
+
var newhref = get_current_state_url();
|
|
75
82
|
Object.entries(kvs).forEach((kv) => {
|
|
76
83
|
if (kv[1].unset && kv[1].unset === true)
|
|
77
84
|
newhref = removeQueryStringParameter(newhref, kv[0]);
|
|
@@ -80,7 +87,7 @@ function set_state_fields(kvs) {
|
|
|
80
87
|
pjax_to(newhref.replace("&&", "&").replace("?&", "?"));
|
|
81
88
|
}
|
|
82
89
|
function unset_state_field(key) {
|
|
83
|
-
pjax_to(removeQueryStringParameter(
|
|
90
|
+
pjax_to(removeQueryStringParameter(get_current_state_url(), key));
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
let loadPage = true;
|
|
@@ -93,7 +100,11 @@ $(function () {
|
|
|
93
100
|
});
|
|
94
101
|
|
|
95
102
|
function pjax_to(href) {
|
|
96
|
-
|
|
103
|
+
let $modal = $("#scmodal");
|
|
104
|
+
const inModal = $modal.length && $modal.hasClass("show")
|
|
105
|
+
let $dest = inModal ? $("#scmodal .modal-body") : $("#page-inner-content");
|
|
106
|
+
|
|
107
|
+
if (!$dest.length) window.location.href = href;
|
|
97
108
|
else {
|
|
98
109
|
loadPage = false;
|
|
99
110
|
$.ajax(href, {
|
|
@@ -101,18 +112,21 @@ function pjax_to(href) {
|
|
|
101
112
|
pjaxpageload: "true",
|
|
102
113
|
},
|
|
103
114
|
success: function (res, textStatus, request) {
|
|
104
|
-
window.history.pushState({ url: href }, "", href);
|
|
115
|
+
if (!inModal) window.history.pushState({ url: href }, "", href);
|
|
105
116
|
setTimeout(() => {
|
|
106
117
|
loadPage = true;
|
|
107
118
|
}, 0);
|
|
108
|
-
if (res.includes("<!--SCPT:")) {
|
|
119
|
+
if (!inModal && res.includes("<!--SCPT:")) {
|
|
109
120
|
const start = res.indexOf("<!--SCPT:");
|
|
110
121
|
const end = res.indexOf("-->", start);
|
|
111
122
|
document.title = res.substring(start + 9, end);
|
|
112
123
|
}
|
|
113
|
-
$
|
|
124
|
+
$dest.html(res);
|
|
114
125
|
initialize_page();
|
|
115
126
|
},
|
|
127
|
+
error: function (res) {
|
|
128
|
+
notifyAlert({ type: "danger", text: res.responseText });
|
|
129
|
+
}
|
|
116
130
|
});
|
|
117
131
|
}
|
|
118
132
|
}
|
|
@@ -120,8 +134,24 @@ function pjax_to(href) {
|
|
|
120
134
|
function href_to(href) {
|
|
121
135
|
window.location.href = href;
|
|
122
136
|
}
|
|
123
|
-
function clear_state() {
|
|
124
|
-
|
|
137
|
+
function clear_state(omit_fields_str) {
|
|
138
|
+
let newUrl = get_current_state_url().split("?")[0]
|
|
139
|
+
const hash = get_current_state_url().split("#")[1]
|
|
140
|
+
if (omit_fields_str) {
|
|
141
|
+
const omit_fields = omit_fields_str.split(',').map(s => s.trim())
|
|
142
|
+
let qs = (get_current_state_url().split("?")[1] || "").split("#")[0]
|
|
143
|
+
let params = new URLSearchParams(qs);
|
|
144
|
+
newUrl = newUrl + '?'
|
|
145
|
+
omit_fields.forEach(f => {
|
|
146
|
+
if (params.get(f))
|
|
147
|
+
newUrl = updateQueryStringParameter(newUrl, f, params.get(f));
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
if (hash)
|
|
152
|
+
newUrl += '#' + hash;
|
|
153
|
+
|
|
154
|
+
pjax_to(newUrl);
|
|
125
155
|
}
|
|
126
156
|
|
|
127
157
|
function ajax_done(res) {
|
|
@@ -210,6 +240,7 @@ function ajax_modal(url, opts = {}) {
|
|
|
210
240
|
var title = request.getResponseHeader("Page-Title");
|
|
211
241
|
if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
|
|
212
242
|
$("#scmodal .modal-body").html(res);
|
|
243
|
+
$("#scmodal").prop("data-modal-state", url);
|
|
213
244
|
new bootstrap.Modal($("#scmodal")).show();
|
|
214
245
|
initialize_page();
|
|
215
246
|
(opts.onOpen || function () { })(res);
|
|
@@ -468,3 +499,7 @@ Copyright (c) 2015 Jeff Green
|
|
|
468
499
|
});
|
|
469
500
|
};
|
|
470
501
|
})(jQuery);
|
|
502
|
+
|
|
503
|
+
// Copyright (c) 2011 Marcus Ekwall, http://writeless.se/
|
|
504
|
+
// https://github.com/mekwall/jquery-throttle
|
|
505
|
+
(function (a) { var b = a.jQuery || a.me || (a.me = {}), i = function (e, f, g, h, c, a) { f || (f = 100); var d = !1, j = !1, i = typeof g === "function", l = function (a, b) { d = setTimeout(function () { d = !1; if (h || c) e.apply(a, b), c && (j = +new Date); i && g.apply(a, b) }, f) }, k = function () { if (!d || a) { if (!d && !h && (!c || +new Date - j > f)) e.apply(this, arguments), c && (j = +new Date); (a || !c) && clearTimeout(d); l(this, arguments) } }; if (b.guid) k.guid = e.guid = e.guid || b.guid++; return k }; b.throttle = i; b.debounce = function (a, b, g, h, c) { return i(a, b, g, h, c, !0) } })(this);
|
package/routes/admin.js
CHANGED
|
@@ -390,7 +390,7 @@ router.get(
|
|
|
390
390
|
renderForm(backupForm, req.csrfToken()),
|
|
391
391
|
a(
|
|
392
392
|
{ href: "/admin/auto-backup-list" },
|
|
393
|
-
"Restore/download automated backups »"
|
|
393
|
+
req.__("Restore/download automated backups »")
|
|
394
394
|
),
|
|
395
395
|
script(
|
|
396
396
|
domReady(
|
|
@@ -406,13 +406,13 @@ router.get(
|
|
|
406
406
|
contents: div(
|
|
407
407
|
p(
|
|
408
408
|
i(
|
|
409
|
-
"Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns)."
|
|
409
|
+
req.__("Snapshots store your application structure and definition, without the table data. Individual views and pages can be restored from snapshots from the <a href='/viewedit'>view</a> or <a href='/pageedit'>pages</a> overviews (\"Restore\" from individual page or view dropdowns).")
|
|
410
410
|
)
|
|
411
411
|
),
|
|
412
412
|
renderForm(aSnapshotForm, req.csrfToken()),
|
|
413
413
|
a(
|
|
414
414
|
{ href: "/admin/snapshot-list" },
|
|
415
|
-
"List/download snapshots »"
|
|
415
|
+
req.__("List/download snapshots »")
|
|
416
416
|
)
|
|
417
417
|
),
|
|
418
418
|
},
|
|
@@ -472,16 +472,16 @@ router.get(
|
|
|
472
472
|
title: req.__("Restoring automated backup"),
|
|
473
473
|
contents: div(
|
|
474
474
|
ol(
|
|
475
|
-
li("Download one of the backups above"),
|
|
475
|
+
li(req.__("Download one of the backups above")),
|
|
476
476
|
li(
|
|
477
|
-
a({ href: "/admin/clear-all" }, "Clear this application"),
|
|
477
|
+
a({ href: "/admin/clear-all" }, req.__("Clear this application")),
|
|
478
478
|
" ",
|
|
479
|
-
|
|
479
|
+
req.__("(tick all boxes)")
|
|
480
480
|
),
|
|
481
481
|
li(
|
|
482
|
-
|
|
482
|
+
req.__("When prompted to create the first user, click the link to restore a backup")
|
|
483
483
|
),
|
|
484
|
-
li("Select the downloaded backup file")
|
|
484
|
+
li(req.__("Select the downloaded backup file"))
|
|
485
485
|
)
|
|
486
486
|
),
|
|
487
487
|
},
|
|
@@ -551,7 +551,7 @@ router.get(
|
|
|
551
551
|
mkTable(
|
|
552
552
|
[
|
|
553
553
|
{
|
|
554
|
-
label: "When",
|
|
554
|
+
label: req.__("When"),
|
|
555
555
|
key: (r) =>
|
|
556
556
|
`${localeDateTime(r.created)} (${moment(r.created).fromNow()})`,
|
|
557
557
|
},
|
|
@@ -618,7 +618,7 @@ const autoBackupForm = (req) =>
|
|
|
618
618
|
noSubmitButton: true,
|
|
619
619
|
additionalButtons: [
|
|
620
620
|
{
|
|
621
|
-
label: "Backup now",
|
|
621
|
+
label: req.__("Backup now"),
|
|
622
622
|
id: "btnBackupNow",
|
|
623
623
|
class: "btn btn-outline-secondary",
|
|
624
624
|
onclick: "ajax_post('/admin/auto-backup-now')",
|
|
@@ -671,7 +671,7 @@ const snapshotForm = (req) =>
|
|
|
671
671
|
noSubmitButton: true,
|
|
672
672
|
additionalButtons: [
|
|
673
673
|
{
|
|
674
|
-
label: "Snapshot now",
|
|
674
|
+
label: req.__("Snapshot now"),
|
|
675
675
|
id: "btnSnapNow",
|
|
676
676
|
class: "btn btn-outline-secondary",
|
|
677
677
|
onclick: "ajax_post('/admin/snapshot-now')",
|
|
@@ -808,7 +808,6 @@ router.get(
|
|
|
808
808
|
" ",
|
|
809
809
|
req.__("Configuration check")
|
|
810
810
|
),
|
|
811
|
-
|
|
812
811
|
hr(),
|
|
813
812
|
|
|
814
813
|
a(
|
|
@@ -945,7 +944,7 @@ router.post(
|
|
|
945
944
|
});
|
|
946
945
|
child.on("exit", function (code, signal) {
|
|
947
946
|
res.end(
|
|
948
|
-
`Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
|
|
947
|
+
req.__(`Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`)
|
|
949
948
|
);
|
|
950
949
|
setTimeout(() => {
|
|
951
950
|
if (process.send) process.send("RestartServer");
|
|
@@ -1193,7 +1192,7 @@ router.get(
|
|
|
1193
1192
|
})
|
|
1194
1193
|
);
|
|
1195
1194
|
/**
|
|
1196
|
-
* /
|
|
1195
|
+
* /configuration-check
|
|
1197
1196
|
*/
|
|
1198
1197
|
router.get(
|
|
1199
1198
|
"/configuration-check",
|
|
@@ -1576,7 +1575,7 @@ router.post(
|
|
|
1576
1575
|
{
|
|
1577
1576
|
type: "card",
|
|
1578
1577
|
title: req.__("Build Result"),
|
|
1579
|
-
contents: div("The build was successfully"),
|
|
1578
|
+
contents: div(req.__("The build was successfully")),
|
|
1580
1579
|
},
|
|
1581
1580
|
files.length > 0 ? app_files_table(files, req) : "",
|
|
1582
1581
|
],
|
|
@@ -1588,7 +1587,7 @@ router.post(
|
|
|
1588
1587
|
type: "card",
|
|
1589
1588
|
title: req.__("Build Result"),
|
|
1590
1589
|
contents: div(
|
|
1591
|
-
"Unable to build the app:",
|
|
1590
|
+
req.__("Unable to build the app:"),
|
|
1592
1591
|
pre(code(childOutputs.join("<br/>")))
|
|
1593
1592
|
),
|
|
1594
1593
|
},
|
|
@@ -1604,7 +1603,7 @@ router.post(
|
|
|
1604
1603
|
type: "card",
|
|
1605
1604
|
title: req.__("Build Result"),
|
|
1606
1605
|
contents: div(
|
|
1607
|
-
p("Unable to build the app:"),
|
|
1606
|
+
p(req.__("Unable to build the app:")),
|
|
1608
1607
|
pre(code(message)),
|
|
1609
1608
|
pre(code(stack))
|
|
1610
1609
|
),
|
package/routes/api.js
CHANGED
|
@@ -71,8 +71,8 @@ function accessAllowedRead(req, user, table) {
|
|
|
71
71
|
req.user && req.user.id
|
|
72
72
|
? req.user.role_id
|
|
73
73
|
: user && user.role_id
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
? user.role_id
|
|
75
|
+
: 10;
|
|
76
76
|
|
|
77
77
|
return role <= table.min_role_read;
|
|
78
78
|
}
|
|
@@ -89,8 +89,8 @@ function accessAllowedWrite(req, user, table) {
|
|
|
89
89
|
req.user && req.user.id
|
|
90
90
|
? req.user.role_id
|
|
91
91
|
: user && user.role_id
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
? user.role_id
|
|
93
|
+
: 10;
|
|
94
94
|
|
|
95
95
|
return role <= table.min_role_write;
|
|
96
96
|
}
|
|
@@ -106,8 +106,8 @@ function accessAllowed(req, user, trigger) {
|
|
|
106
106
|
req.user && req.user.id
|
|
107
107
|
? req.user.role_id
|
|
108
108
|
: user && user.role_id
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
? user.role_id
|
|
110
|
+
: 10;
|
|
111
111
|
|
|
112
112
|
return role <= trigger.min_role;
|
|
113
113
|
}
|
|
@@ -247,6 +247,7 @@ router.get(
|
|
|
247
247
|
fields: tbl_fields,
|
|
248
248
|
approximate: !!approximate,
|
|
249
249
|
state: req_query,
|
|
250
|
+
table
|
|
250
251
|
});
|
|
251
252
|
rows = await table.getRows(qstate);
|
|
252
253
|
} else {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const Page = require("@saltcorn/data/models/page");
|
|
2
|
+
const {
|
|
3
|
+
buildObjectTrees,
|
|
4
|
+
} = require("@saltcorn/data/diagram/node_extract_utils");
|
|
5
|
+
const { generateCyCode } = require("@saltcorn/data/diagram/cy_generate_utils");
|
|
6
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
7
|
+
const { div, script, domReady } = require("@saltcorn/markup/tags");
|
|
8
|
+
const { isAdmin, error_catcher } = require("./utils.js");
|
|
9
|
+
const Router = require("express-promise-router");
|
|
10
|
+
|
|
11
|
+
const router = new Router();
|
|
12
|
+
module.exports = router;
|
|
13
|
+
|
|
14
|
+
router.get(
|
|
15
|
+
"/",
|
|
16
|
+
isAdmin,
|
|
17
|
+
error_catcher(async (req, res) => {
|
|
18
|
+
const modernCfg = getState().getConfig("home_page_by_role");
|
|
19
|
+
let pages = null;
|
|
20
|
+
if (modernCfg) {
|
|
21
|
+
pages = Object.values(modernCfg)
|
|
22
|
+
.filter((val) => val)
|
|
23
|
+
.map((val) => Page.findOne({ name: val }));
|
|
24
|
+
} else {
|
|
25
|
+
pages = new Array();
|
|
26
|
+
for (const legacyRole of ["public", "user", "staff", "admin"]) {
|
|
27
|
+
const page = await Page.findOne({ name: `${legacyRole}_home` });
|
|
28
|
+
if (page) pages.push(page);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const cyCode = generateCyCode(await buildObjectTrees(pages));
|
|
32
|
+
res.sendWrap(
|
|
33
|
+
{
|
|
34
|
+
title: req.__(`Application diagram`),
|
|
35
|
+
headers: [
|
|
36
|
+
{
|
|
37
|
+
script:
|
|
38
|
+
"https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.22.1/cytoscape.min.js",
|
|
39
|
+
style: `
|
|
40
|
+
#cy {
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 900px;
|
|
43
|
+
display: block;
|
|
44
|
+
}`,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
above: [
|
|
50
|
+
{
|
|
51
|
+
type: "card",
|
|
52
|
+
title: req.__(`Application diagram`),
|
|
53
|
+
contents: [div({ id: "cy" }), script(domReady(cyCode))],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
})
|
|
59
|
+
);
|
package/routes/fields.js
CHANGED
|
@@ -19,12 +19,13 @@ const {
|
|
|
19
19
|
expressionValidator,
|
|
20
20
|
get_async_expression_function,
|
|
21
21
|
get_expression_function,
|
|
22
|
+
freeVariables,
|
|
22
23
|
} = require("@saltcorn/data/models/expression");
|
|
23
24
|
const db = require("@saltcorn/data/db");
|
|
24
25
|
|
|
25
26
|
const { isAdmin, error_catcher } = require("./utils.js");
|
|
26
27
|
const expressionBlurb = require("../markup/expression_blurb");
|
|
27
|
-
const { readState } = require("@saltcorn/data/plugin-helper");
|
|
28
|
+
const { readState, add_free_variables_to_joinfields } = require("@saltcorn/data/plugin-helper");
|
|
28
29
|
const { wizardCardTitle } = require("../markup/forms.js");
|
|
29
30
|
const FieldRepeat = require("@saltcorn/data/models/fieldrepeat");
|
|
30
31
|
const { applyAsync } = require("@saltcorn/data/utils");
|
|
@@ -621,7 +622,11 @@ router.post(
|
|
|
621
622
|
const { formula, tablename, stored } = req.body;
|
|
622
623
|
const table = await Table.findOne({ name: tablename });
|
|
623
624
|
const fields = await table.getFields();
|
|
624
|
-
const
|
|
625
|
+
const freeVars = freeVariables(formula)
|
|
626
|
+
const joinFields = {}
|
|
627
|
+
if (stored)
|
|
628
|
+
add_free_variables_to_joinfields(freeVars, joinFields, fields)
|
|
629
|
+
const rows = await table.getJoinedRows({ joinFields, orderBy: "RANDOM()", limit: 1 });
|
|
625
630
|
if (rows.length < 1) return "No rows in table";
|
|
626
631
|
let result;
|
|
627
632
|
try {
|
|
@@ -661,8 +666,12 @@ router.post(
|
|
|
661
666
|
return;
|
|
662
667
|
}
|
|
663
668
|
const fields = await table.getFields();
|
|
664
|
-
|
|
665
|
-
|
|
669
|
+
let row = { ...req.body };
|
|
670
|
+
if (!row || Object.keys(row).length === 0) {
|
|
671
|
+
const { id } = req.query
|
|
672
|
+
if (id) row = await table.getRow({ id })
|
|
673
|
+
} else
|
|
674
|
+
readState(row, fields);
|
|
666
675
|
|
|
667
676
|
if (fieldName.includes(".")) {
|
|
668
677
|
//join field
|
|
@@ -694,7 +703,7 @@ router.post(
|
|
|
694
703
|
return;
|
|
695
704
|
}
|
|
696
705
|
} else {
|
|
697
|
-
targetField.type.fieldviews[fieldview];
|
|
706
|
+
fv = targetField.type.fieldviews[fieldview];
|
|
698
707
|
if (!fv)
|
|
699
708
|
fv =
|
|
700
709
|
targetField.type.fieldviews.show ||
|
|
@@ -743,7 +752,9 @@ router.post(
|
|
|
743
752
|
|
|
744
753
|
let result;
|
|
745
754
|
try {
|
|
746
|
-
if (field.
|
|
755
|
+
if (!field.calculated) {
|
|
756
|
+
result = row[field.name]
|
|
757
|
+
} else if (field.stored) {
|
|
747
758
|
const f = get_async_expression_function(formula, fields);
|
|
748
759
|
result = await f(row);
|
|
749
760
|
} else {
|
|
@@ -751,7 +762,9 @@ router.post(
|
|
|
751
762
|
result = f(row);
|
|
752
763
|
}
|
|
753
764
|
const fv = field.type.fieldviews[fieldview];
|
|
754
|
-
|
|
765
|
+
if (!fv)
|
|
766
|
+
res.send(text(result));
|
|
767
|
+
else res.send(fv.run(result));
|
|
755
768
|
} catch (e) {
|
|
756
769
|
return res.status(400).send(`Error: ${e.message}`);
|
|
757
770
|
}
|
package/routes/index.js
CHANGED
|
@@ -72,6 +72,7 @@ const roleadmin = require("../auth/roleadmin");
|
|
|
72
72
|
const scapi = require("./scapi");
|
|
73
73
|
const tags = require("./tags");
|
|
74
74
|
const tagentries = require("./tag_entries");
|
|
75
|
+
const dataDiagram = require("./diagram");
|
|
75
76
|
|
|
76
77
|
module.exports =
|
|
77
78
|
/**
|
|
@@ -110,4 +111,5 @@ module.exports =
|
|
|
110
111
|
app.use("/scapi", scapi);
|
|
111
112
|
app.use("/tag", tags);
|
|
112
113
|
app.use("/tag-entries", tagentries);
|
|
114
|
+
app.use("/diagram", dataDiagram);
|
|
113
115
|
};
|
package/routes/pageedit.js
CHANGED
|
@@ -185,9 +185,8 @@ const pageBuilderData = async (req, context) => {
|
|
|
185
185
|
const getRootPageForm = (pages, roles, req) => {
|
|
186
186
|
const form = new Form({
|
|
187
187
|
action: "/pageedit/set_root_page",
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
onChange: "remove_outline(this)",
|
|
188
|
+
noSubmitButton: true,
|
|
189
|
+
onChange: "saveAndContinue(this)",
|
|
191
190
|
blurb: req.__(
|
|
192
191
|
"The root page is the page that is served when the user visits the home location (/). This can be set for each user role."
|
|
193
192
|
),
|
package/routes/plugins.js
CHANGED
|
@@ -455,7 +455,7 @@ const store_actions_dropdown = (req) =>
|
|
|
455
455
|
{
|
|
456
456
|
class: "dropdown-item",
|
|
457
457
|
href: `/plugins/upgrade`,
|
|
458
|
-
onClick: `notifyAlert('Upgrading modules...', true)`,
|
|
458
|
+
onClick: `notifyAlert('${req.__("Upgrading modules...")}', true)`,
|
|
459
459
|
},
|
|
460
460
|
'<i class="far fa-arrow-alt-circle-up"></i> ' +
|
|
461
461
|
req.__("Upgrade installed modules")
|
|
@@ -551,7 +551,7 @@ router.get(
|
|
|
551
551
|
const { name } = req.params;
|
|
552
552
|
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
553
553
|
if (!plugin) {
|
|
554
|
-
req.flash("warning", "
|
|
554
|
+
req.flash("warning", req.__("Module not found"));
|
|
555
555
|
res.redirect("/plugins");
|
|
556
556
|
return;
|
|
557
557
|
}
|
|
@@ -792,7 +792,7 @@ router.get(
|
|
|
792
792
|
pkgjson = require(path.join(mod.location, "package.json"));
|
|
793
793
|
|
|
794
794
|
if (!plugin_db) {
|
|
795
|
-
req.flash("warning", "
|
|
795
|
+
req.flash("warning", "Module not found");
|
|
796
796
|
res.redirect("/plugins");
|
|
797
797
|
return;
|
|
798
798
|
}
|
|
@@ -817,7 +817,7 @@ router.get(
|
|
|
817
817
|
),
|
|
818
818
|
mod.plugin_module.dependencies
|
|
819
819
|
? tr(
|
|
820
|
-
th(req.__("
|
|
820
|
+
th(req.__("Module dependencies")),
|
|
821
821
|
td(
|
|
822
822
|
mod.plugin_module.dependencies.map((d) =>
|
|
823
823
|
span({ class: "badge bg-primary me-1" }, d)
|
|
@@ -933,7 +933,7 @@ router.get(
|
|
|
933
933
|
|
|
934
934
|
const plugin = await Plugin.findOne({ name });
|
|
935
935
|
await plugin.upgrade_version((p, f) => load_plugins.loadPlugin(p, f));
|
|
936
|
-
req.flash("success", req.__(`
|
|
936
|
+
req.flash("success", req.__(`Module up-to-date`));
|
|
937
937
|
|
|
938
938
|
res.redirect(`/plugins/info/${plugin.name}`);
|
|
939
939
|
})
|
|
@@ -954,7 +954,7 @@ router.post(
|
|
|
954
954
|
if (schema !== db.connectObj.default_schema) {
|
|
955
955
|
req.flash(
|
|
956
956
|
"error",
|
|
957
|
-
req.__(`Only store
|
|
957
|
+
req.__(`Only store modules can be installed on tenant instances`)
|
|
958
958
|
);
|
|
959
959
|
res.redirect(`/plugins`);
|
|
960
960
|
} else {
|
|
@@ -963,12 +963,12 @@ router.post(
|
|
|
963
963
|
plugin,
|
|
964
964
|
schema === db.connectObj.default_schema || plugin.source === "github"
|
|
965
965
|
);
|
|
966
|
-
req.flash("success", req.__(`
|
|
966
|
+
req.flash("success", req.__(`Module %s installed`, plugin.name));
|
|
967
967
|
res.redirect(`/plugins`);
|
|
968
968
|
} catch (e) {
|
|
969
969
|
req.flash("error", `${e.message}`);
|
|
970
970
|
const form = pluginForm(req, plugin);
|
|
971
|
-
res.sendWrap(req.__(`Edit
|
|
971
|
+
res.sendWrap(req.__(`Edit Module`), renderForm(form, req.csrfToken()));
|
|
972
972
|
}
|
|
973
973
|
}
|
|
974
974
|
})
|
|
@@ -988,7 +988,7 @@ router.post(
|
|
|
988
988
|
|
|
989
989
|
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
990
990
|
if (!plugin) {
|
|
991
|
-
req.flash("warning", "
|
|
991
|
+
req.flash("warning", "Module not found");
|
|
992
992
|
res.redirect("/plugins");
|
|
993
993
|
return;
|
|
994
994
|
}
|
|
@@ -998,11 +998,11 @@ router.post(
|
|
|
998
998
|
getState().getConfig("development_mode", false)
|
|
999
999
|
) {
|
|
1000
1000
|
await plugin.delete();
|
|
1001
|
-
req.flash("success", req.__(`
|
|
1001
|
+
req.flash("success", req.__(`Module %s removed.`, plugin.name));
|
|
1002
1002
|
} else {
|
|
1003
1003
|
req.flash(
|
|
1004
1004
|
"error",
|
|
1005
|
-
req.__(`Cannot remove
|
|
1005
|
+
req.__(`Cannot remove module: views %s depend on it`, depviews.join())
|
|
1006
1006
|
);
|
|
1007
1007
|
}
|
|
1008
1008
|
res.redirect(`/plugins`);
|
|
@@ -1025,7 +1025,7 @@ router.post(
|
|
|
1025
1025
|
if (!plugin) {
|
|
1026
1026
|
req.flash(
|
|
1027
1027
|
"error",
|
|
1028
|
-
req.__(`
|
|
1028
|
+
req.__(`Module %s not found`, text(decodeURIComponent(name)))
|
|
1029
1029
|
);
|
|
1030
1030
|
res.redirect(`/plugins`);
|
|
1031
1031
|
return;
|
|
@@ -1034,7 +1034,7 @@ router.post(
|
|
|
1034
1034
|
if (!isRoot && plugin.unsafe) {
|
|
1035
1035
|
req.flash(
|
|
1036
1036
|
"error",
|
|
1037
|
-
req.__("Cannot install unsafe
|
|
1037
|
+
req.__("Cannot install unsafe modules on subdomain tenants")
|
|
1038
1038
|
);
|
|
1039
1039
|
res.redirect(`/plugins`);
|
|
1040
1040
|
return;
|
|
@@ -1047,14 +1047,14 @@ router.post(
|
|
|
1047
1047
|
req.flash(
|
|
1048
1048
|
"success",
|
|
1049
1049
|
req.__(
|
|
1050
|
-
`
|
|
1050
|
+
`Module %s installed, please complete configuration.`,
|
|
1051
1051
|
plugin_db.name
|
|
1052
1052
|
)
|
|
1053
1053
|
);
|
|
1054
1054
|
await sleep(1000); // Allow other workers to load this plugin
|
|
1055
1055
|
res.redirect(`/plugins/configure/${plugin_db.name}`);
|
|
1056
1056
|
} else {
|
|
1057
|
-
req.flash("success", req.__(`
|
|
1057
|
+
req.flash("success", req.__(`Module %s installed`, plugin.name));
|
|
1058
1058
|
res.redirect(`/plugins`);
|
|
1059
1059
|
}
|
|
1060
1060
|
})
|
package/routes/tables.js
CHANGED
|
@@ -1013,7 +1013,6 @@ router.get(
|
|
|
1013
1013
|
const getRole = (rid) => roles.find((r) => r.id === rid).role;
|
|
1014
1014
|
const mainCard = await tablesList(rows, req);
|
|
1015
1015
|
const createCard = div(
|
|
1016
|
-
h5(req.__("Create table")),
|
|
1017
1016
|
a(
|
|
1018
1017
|
{ href: `/table/new`, class: "btn btn-primary mt-1 me-3" },
|
|
1019
1018
|
i({ class: "fas fa-plus-square me-1" }),
|
|
@@ -1029,8 +1028,13 @@ router.get(
|
|
|
1029
1028
|
),
|
|
1030
1029
|
!db.isSQLite &&
|
|
1031
1030
|
a(
|
|
1032
|
-
{
|
|
1031
|
+
{
|
|
1032
|
+
href: `/table/discover`,
|
|
1033
|
+
class: "btn btn-secondary mt-1",
|
|
1034
|
+
title: req.__("Discover tables that are already in the Database, but not known to Saltcorn"),
|
|
1035
|
+
},
|
|
1033
1036
|
i({ class: "fas fa-map-signs me-1" }),
|
|
1037
|
+
|
|
1034
1038
|
req.__("Discover tables")
|
|
1035
1039
|
)
|
|
1036
1040
|
);
|
package/routes/tag_entries.js
CHANGED
|
@@ -105,7 +105,7 @@ router.get(
|
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
type: "card",
|
|
108
|
-
title: `Add entries to tag
|
|
108
|
+
title: req.__(`Add entries to tag`),
|
|
109
109
|
contents: buildForm(
|
|
110
110
|
entry_type,
|
|
111
111
|
tag_id,
|
|
@@ -143,7 +143,7 @@ router.post(
|
|
|
143
143
|
const { entry_type, tag_id } = req.params;
|
|
144
144
|
const { ids } = req.body;
|
|
145
145
|
if (!ids) {
|
|
146
|
-
req.flash("error", req.__("Please select at least
|
|
146
|
+
req.flash("error", req.__("Please select at least one item"));
|
|
147
147
|
return res.redirect(`/tag-entries/add/${entry_type}/${tag_id}`);
|
|
148
148
|
}
|
|
149
149
|
const fieldName = idField(entry_type);
|
package/routes/tags.js
CHANGED
|
@@ -6,7 +6,7 @@ const Form = require("@saltcorn/data/models/form");
|
|
|
6
6
|
const User = require("@saltcorn/data/models/user");
|
|
7
7
|
|
|
8
8
|
const { isAdmin, error_catcher, csrfField } = require("./utils");
|
|
9
|
-
const {
|
|
9
|
+
const { send_infoarch_page } = require("../markup/admin");
|
|
10
10
|
|
|
11
11
|
const {
|
|
12
12
|
mkTable,
|
|
@@ -31,7 +31,7 @@ router.get(
|
|
|
31
31
|
isAdmin,
|
|
32
32
|
error_catcher(async (req, res) => {
|
|
33
33
|
const rows = await Tag.find();
|
|
34
|
-
|
|
34
|
+
send_infoarch_page({
|
|
35
35
|
res,
|
|
36
36
|
req,
|
|
37
37
|
active_sub: "Tags",
|
|
@@ -199,7 +199,7 @@ router.get(
|
|
|
199
199
|
href: `/tag-entries/add/pages/${tag.id}`,
|
|
200
200
|
class: "btn btn-primary",
|
|
201
201
|
},
|
|
202
|
-
req.__("Add
|
|
202
|
+
req.__("Add pages")
|
|
203
203
|
),
|
|
204
204
|
],
|
|
205
205
|
},
|
|
@@ -222,7 +222,7 @@ router.get(
|
|
|
222
222
|
href: `/tag-entries/add/trigger/${tag.id}`,
|
|
223
223
|
class: "btn btn-primary",
|
|
224
224
|
},
|
|
225
|
-
req.__("Add
|
|
225
|
+
req.__("Add triggers")
|
|
226
226
|
),
|
|
227
227
|
],
|
|
228
228
|
},
|
package/tests/page.test.js
CHANGED
|
@@ -146,6 +146,15 @@ describe("pageedit", () => {
|
|
|
146
146
|
.send("id=1")
|
|
147
147
|
.expect(toRedirect("/pageedit/"));
|
|
148
148
|
});
|
|
149
|
+
it("show builder", async () => {
|
|
150
|
+
const app = await getApp({ disableCsrf: true });
|
|
151
|
+
const loginCookie = await getAdminLoginCookie();
|
|
152
|
+
await request(app)
|
|
153
|
+
.get("/pageedit/edit/a_page")
|
|
154
|
+
.set("Cookie", loginCookie)
|
|
155
|
+
.expect(toInclude("<script>builder.renderBuilder"));
|
|
156
|
+
|
|
157
|
+
});
|
|
149
158
|
|
|
150
159
|
it("sets root page", async () => {
|
|
151
160
|
const app = await getApp({ disableCsrf: true });
|
package/tests/viewedit.test.js
CHANGED
|
@@ -43,6 +43,22 @@ describe("viewedit edit endpoint", () => {
|
|
|
43
43
|
.set("Cookie", loginCookie)
|
|
44
44
|
.expect(toInclude("author"));
|
|
45
45
|
});
|
|
46
|
+
it("show list editor", async () => {
|
|
47
|
+
const loginCookie = await getAdminLoginCookie();
|
|
48
|
+
const app = await getApp({ disableCsrf: true });
|
|
49
|
+
await request(app)
|
|
50
|
+
.get("/viewedit/config/authorlist")
|
|
51
|
+
.set("Cookie", loginCookie)
|
|
52
|
+
.expect(toInclude("author"));
|
|
53
|
+
});
|
|
54
|
+
it("show builder", async () => {
|
|
55
|
+
const loginCookie = await getAdminLoginCookie();
|
|
56
|
+
const app = await getApp({ disableCsrf: true });
|
|
57
|
+
await request(app)
|
|
58
|
+
.get("/viewedit/config/authorshow")
|
|
59
|
+
.set("Cookie", loginCookie)
|
|
60
|
+
.expect(toInclude("<script>builder.renderBuilder"));
|
|
61
|
+
});
|
|
46
62
|
});
|
|
47
63
|
|
|
48
64
|
describe("viewedit new List", () => {
|