@saltcorn/server 1.0.0-beta.9 → 1.0.0-rc.10
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 +4 -7
- package/auth/admin.js +1 -0
- package/auth/routes.js +4 -1
- package/help/Aggregation where formula.tmd +11 -0
- package/help/Calculated fields.tmd +28 -0
- package/help/Date format.tmd +55 -0
- package/help/Protected fields.tmd +4 -0
- package/help/Snapshots.tmd +19 -0
- package/help/Table history.tmd +42 -0
- package/help/index.js +4 -1
- package/load_plugins.js +90 -8
- package/locales/en.json +14 -1
- package/locales/pl.json +407 -85
- package/package.json +11 -11
- package/public/gridedit.js +3 -1
- package/public/saltcorn-builder.css +0 -7
- package/public/saltcorn-common.js +196 -101
- package/public/saltcorn.css +17 -0
- package/public/saltcorn.js +91 -17
- package/routes/actions.js +19 -25
- package/routes/admin.js +256 -54
- package/routes/api.js +36 -1
- package/routes/delete.js +4 -1
- package/routes/eventlog.js +2 -1
- package/routes/fields.js +21 -5
- package/routes/files.js +9 -2
- package/routes/infoarch.js +40 -2
- package/routes/list.js +1 -1
- package/routes/menu.js +3 -0
- package/routes/pageedit.js +9 -2
- package/routes/plugins.js +124 -28
- package/routes/scapi.js +19 -0
- package/routes/search.js +6 -1
- package/routes/sync.js +3 -5
- package/routes/tables.js +14 -4
- package/routes/viewedit.js +7 -4
- package/serve.js +17 -3
- package/tests/api.test.js +29 -0
- package/tests/fields.test.js +32 -0
- package/tests/plugin_install.test.js +235 -0
- package/tests/plugins.test.js +140 -0
package/public/saltcorn.js
CHANGED
|
@@ -190,7 +190,8 @@ function pjax_to(href, e) {
|
|
|
190
190
|
initialize_page();
|
|
191
191
|
},
|
|
192
192
|
error: function (res) {
|
|
193
|
-
|
|
193
|
+
if (!checkNetworkError(res))
|
|
194
|
+
notifyAlert({ type: "danger", text: res.responseText });
|
|
194
195
|
},
|
|
195
196
|
});
|
|
196
197
|
}
|
|
@@ -224,8 +225,10 @@ function ajax_done(res, viewname) {
|
|
|
224
225
|
function spin_action_link(e) {
|
|
225
226
|
const $e = $(e);
|
|
226
227
|
const width = $e.width();
|
|
228
|
+
const height = $e.height();
|
|
229
|
+
|
|
227
230
|
$e.attr("data-innerhtml-prespin", $e.html());
|
|
228
|
-
$e.html('<i class="fas fa-spinner fa-spin"></i>').width(width);
|
|
231
|
+
$e.html('<i class="fas fa-spinner fa-spin"></i>').width(width).height(height);
|
|
229
232
|
}
|
|
230
233
|
|
|
231
234
|
function reset_spinners() {
|
|
@@ -264,7 +267,8 @@ function view_post(viewnameOrElem, route, data, onDone, sendState) {
|
|
|
264
267
|
reset_spinners();
|
|
265
268
|
})
|
|
266
269
|
.fail(function (res) {
|
|
267
|
-
|
|
270
|
+
if (!checkNetworkError(res))
|
|
271
|
+
notifyAlert({ type: "danger", text: res.responseText });
|
|
268
272
|
reset_spinners();
|
|
269
273
|
});
|
|
270
274
|
}
|
|
@@ -346,16 +350,18 @@ function expand_thumbnail(img_id, filename) {
|
|
|
346
350
|
}
|
|
347
351
|
|
|
348
352
|
function ajax_modal(url, opts = {}) {
|
|
349
|
-
ensure_modal_exists_and_closed();
|
|
350
|
-
$("#scmodal").removeClass("no-submit-reload");
|
|
351
|
-
$("#scmodal").attr("data-on-close-reload-view", opts.reload_view || null);
|
|
352
|
-
|
|
353
|
-
if (opts.submitReload === false) $("#scmodal").addClass("no-submit-reload");
|
|
354
353
|
$.ajax(url, {
|
|
355
354
|
headers: {
|
|
356
355
|
SaltcornModalRequest: "true",
|
|
357
356
|
},
|
|
358
357
|
success: function (res, textStatus, request) {
|
|
358
|
+
ensure_modal_exists_and_closed();
|
|
359
|
+
$("#scmodal").removeClass("no-submit-reload");
|
|
360
|
+
$("#scmodal").attr("data-on-close-reload-view", opts.reload_view || null);
|
|
361
|
+
|
|
362
|
+
if (opts.submitReload === false)
|
|
363
|
+
$("#scmodal").addClass("no-submit-reload");
|
|
364
|
+
|
|
359
365
|
var title = request.getResponseHeader("Page-Title");
|
|
360
366
|
var width = request.getResponseHeader("SaltcornModalWidth");
|
|
361
367
|
var minwidth = request.getResponseHeader("SaltcornModalMinWidth");
|
|
@@ -388,7 +394,7 @@ function ajax_modal(url, opts = {}) {
|
|
|
388
394
|
? {
|
|
389
395
|
error: opts.onError,
|
|
390
396
|
}
|
|
391
|
-
: {}),
|
|
397
|
+
: { error: checkNetworkError }),
|
|
392
398
|
});
|
|
393
399
|
}
|
|
394
400
|
function closeModal() {
|
|
@@ -426,6 +432,20 @@ function saveAndContinue(e, k, event) {
|
|
|
426
432
|
)
|
|
427
433
|
return;
|
|
428
434
|
var form = $(e).closest("form");
|
|
435
|
+
let focusedEl = null;
|
|
436
|
+
if (!event || !event.srcElement) {
|
|
437
|
+
const el = form.find("select[sc-received-focus]")[0];
|
|
438
|
+
if (el) {
|
|
439
|
+
el.removeAttribute("sc-received-focus");
|
|
440
|
+
if (el.getAttribute("previous-val") === el.value) return;
|
|
441
|
+
}
|
|
442
|
+
} else if (
|
|
443
|
+
event.srcElement.tagName === "SELECT" &&
|
|
444
|
+
event.srcElement.getAttribute("previous-val") !== undefined
|
|
445
|
+
) {
|
|
446
|
+
focusedEl = event.srcElement;
|
|
447
|
+
}
|
|
448
|
+
|
|
429
449
|
const valres = form[0].reportValidity();
|
|
430
450
|
if (!valres) return;
|
|
431
451
|
submitWithEmptyAction(form[0]);
|
|
@@ -448,10 +468,12 @@ function saveAndContinue(e, k, event) {
|
|
|
448
468
|
reloadEmbeddedEditOwnViews(form, res.id);
|
|
449
469
|
}
|
|
450
470
|
common_done(res, form.attr("data-viewname"));
|
|
471
|
+
if (focusedEl) focusedEl.setAttribute("previous-val", focusedEl.value);
|
|
451
472
|
},
|
|
452
473
|
error: function (request) {
|
|
453
474
|
var ct = request.getResponseHeader("content-type") || "";
|
|
454
|
-
if (
|
|
475
|
+
if (checkNetworkError(request)) {
|
|
476
|
+
} else if (ct.startsWith && ct.startsWith("application/json")) {
|
|
455
477
|
notifyAlert({ type: "danger", text: request.responseJSON.error });
|
|
456
478
|
} else {
|
|
457
479
|
$("#page-inner-content").html(request.responseText);
|
|
@@ -500,6 +522,7 @@ function applyViewConfig(e, url, k, event) {
|
|
|
500
522
|
},
|
|
501
523
|
data: JSON.stringify(cfg),
|
|
502
524
|
error: function (request) {
|
|
525
|
+
checkNetworkError(request);
|
|
503
526
|
window.savingViewConfig = false;
|
|
504
527
|
ajax_indicate_error(e, request);
|
|
505
528
|
},
|
|
@@ -578,6 +601,7 @@ function ajaxSubmitForm(e, force_no_reload) {
|
|
|
578
601
|
else common_done(res, form.attr("data-viewname"));
|
|
579
602
|
},
|
|
580
603
|
error: function (request) {
|
|
604
|
+
checkNetworkError(request);
|
|
581
605
|
var title = request.getResponseHeader("Page-Title");
|
|
582
606
|
if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
|
|
583
607
|
var body = request.responseText;
|
|
@@ -594,6 +618,9 @@ function ajax_post_json(url, data, args = {}) {
|
|
|
594
618
|
...args,
|
|
595
619
|
});
|
|
596
620
|
}
|
|
621
|
+
|
|
622
|
+
let scNetworkErrorSignaled = false;
|
|
623
|
+
|
|
597
624
|
function ajax_post(url, args) {
|
|
598
625
|
$.ajax(url, {
|
|
599
626
|
type: "POST",
|
|
@@ -603,10 +630,31 @@ function ajax_post(url, args) {
|
|
|
603
630
|
...(args || {}),
|
|
604
631
|
})
|
|
605
632
|
.done(ajax_done)
|
|
606
|
-
.fail((e) =>
|
|
607
|
-
|
|
608
|
-
|
|
633
|
+
.fail((e, ...more) => {
|
|
634
|
+
if (!checkNetworkError(e))
|
|
635
|
+
return ajax_done(
|
|
636
|
+
e.responseJSON || { error: "Unknown error: " + e.responseText }
|
|
637
|
+
);
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function checkNetworkError(e) {
|
|
642
|
+
if (e.readyState == 0 && !e.responseText && !e.responseJSON) {
|
|
643
|
+
//network error
|
|
644
|
+
if (scNetworkErrorSignaled) return true;
|
|
645
|
+
scNetworkErrorSignaled = true;
|
|
646
|
+
setTimeout(() => {
|
|
647
|
+
scNetworkErrorSignaled = false;
|
|
648
|
+
}, 1000);
|
|
649
|
+
console.error("Network error", e);
|
|
650
|
+
notifyAlert({
|
|
651
|
+
type: "danger",
|
|
652
|
+
text: "Network connection error",
|
|
653
|
+
});
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
609
656
|
}
|
|
657
|
+
|
|
610
658
|
function ajax_post_btn(e, reload_on_done, reload_delay) {
|
|
611
659
|
var form = $(e).closest("form");
|
|
612
660
|
var url = form.attr("action");
|
|
@@ -620,6 +668,7 @@ function ajax_post_btn(e, reload_on_done, reload_delay) {
|
|
|
620
668
|
success: function () {
|
|
621
669
|
if (reload_on_done) location.reload();
|
|
622
670
|
},
|
|
671
|
+
error: checkNetworkError,
|
|
623
672
|
complete: function () {
|
|
624
673
|
if (reload_delay)
|
|
625
674
|
setTimeout(function () {
|
|
@@ -641,6 +690,7 @@ function api_action_call(name, body) {
|
|
|
641
690
|
success: function (res) {
|
|
642
691
|
common_done(res.data);
|
|
643
692
|
},
|
|
693
|
+
error: checkNetworkError,
|
|
644
694
|
});
|
|
645
695
|
}
|
|
646
696
|
|
|
@@ -840,7 +890,7 @@ function build_mobile_app(button) {
|
|
|
840
890
|
|
|
841
891
|
if (
|
|
842
892
|
params.useDocker &&
|
|
843
|
-
!cordovaBuilderAvailable &&
|
|
893
|
+
!window.cordovaBuilderAvailable &&
|
|
844
894
|
!confirm(
|
|
845
895
|
"Docker is selected but the Cordova builder seems not to be installed. " +
|
|
846
896
|
"Do you really want to continue?"
|
|
@@ -848,6 +898,30 @@ function build_mobile_app(button) {
|
|
|
848
898
|
) {
|
|
849
899
|
return;
|
|
850
900
|
}
|
|
901
|
+
if (
|
|
902
|
+
isSbadmin2 &&
|
|
903
|
+
!confirm(
|
|
904
|
+
"It seems you are using the standard sbadmin2 layout. " +
|
|
905
|
+
"This layout is not optimized for mobile, consider using any-bootstrap-theme. " +
|
|
906
|
+
"Do you really want to continue?"
|
|
907
|
+
)
|
|
908
|
+
) {
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const notSupportedPlugins = params.includedPlugins.filter(
|
|
912
|
+
(plugin) => !window.pluginsReadyForMobile.includes(plugin)
|
|
913
|
+
);
|
|
914
|
+
if (
|
|
915
|
+
notSupportedPlugins.length > 0 &&
|
|
916
|
+
!confirm(
|
|
917
|
+
`It seems that the plugins '${notSupportedPlugins.join(
|
|
918
|
+
", "
|
|
919
|
+
)}' are not ready for mobile. Do you really want to continue?`
|
|
920
|
+
)
|
|
921
|
+
) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
851
925
|
ajax_post("/admin/build-mobile-app", {
|
|
852
926
|
data: params,
|
|
853
927
|
success: (data) => {
|
|
@@ -919,8 +993,8 @@ function check_cordova_builder() {
|
|
|
919
993
|
$.ajax("/admin/mobile-app/check-cordova-builder", {
|
|
920
994
|
type: "GET",
|
|
921
995
|
success: function (res) {
|
|
922
|
-
cordovaBuilderAvailable = !!res.installed;
|
|
923
|
-
if (cordovaBuilderAvailable) {
|
|
996
|
+
window.cordovaBuilderAvailable = !!res.installed;
|
|
997
|
+
if (window.cordovaBuilderAvailable) {
|
|
924
998
|
$("#dockerBuilderStatusId").html(
|
|
925
999
|
`<span>
|
|
926
1000
|
installed<i class="ps-2 fas fa-check text-success"></i>
|
|
@@ -1014,7 +1088,7 @@ function toggle_tbl_sync() {
|
|
|
1014
1088
|
function toggle_android_platform() {
|
|
1015
1089
|
if ($("#androidCheckboxId")[0].checked === true) {
|
|
1016
1090
|
$("#dockerCheckboxId").attr("hidden", false);
|
|
1017
|
-
$("#dockerCheckboxId").attr("checked", cordovaBuilderAvailable);
|
|
1091
|
+
$("#dockerCheckboxId").attr("checked", window.cordovaBuilderAvailable);
|
|
1018
1092
|
$("#dockerLabelId").removeClass("d-none");
|
|
1019
1093
|
} else {
|
|
1020
1094
|
$("#dockerCheckboxId").attr("hidden", true);
|
package/routes/actions.js
CHANGED
|
@@ -57,21 +57,6 @@ const {
|
|
|
57
57
|
blocklyToolbox,
|
|
58
58
|
} = require("../markup/blockly.js");
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
* @returns {Promise<object>}
|
|
62
|
-
*/
|
|
63
|
-
const getActions = async () => {
|
|
64
|
-
return Object.entries(getState().actions).map(([k, v]) => {
|
|
65
|
-
const hasConfig = !!v.configFields;
|
|
66
|
-
const requireRow = !!v.requireRow;
|
|
67
|
-
return {
|
|
68
|
-
name: k,
|
|
69
|
-
hasConfig,
|
|
70
|
-
requireRow,
|
|
71
|
-
};
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
|
|
75
60
|
/**
|
|
76
61
|
* Show list of Actions (Triggers) (HTTP GET)
|
|
77
62
|
* @name get
|
|
@@ -96,7 +81,7 @@ router.get(
|
|
|
96
81
|
triggers = triggers.filter((t) => tagged_trigger_ids.has(t.id));
|
|
97
82
|
filterOnTag = await Tag.findOne({ id: +req.query._tag });
|
|
98
83
|
}
|
|
99
|
-
const actions =
|
|
84
|
+
const actions = Trigger.abbreviated_actions;
|
|
100
85
|
send_events_page({
|
|
101
86
|
res,
|
|
102
87
|
req,
|
|
@@ -156,7 +141,7 @@ const triggerForm = async (req, trigger) => {
|
|
|
156
141
|
value: r.id,
|
|
157
142
|
label: r.role,
|
|
158
143
|
}));
|
|
159
|
-
const actions =
|
|
144
|
+
const actions = Trigger.abbreviated_actions;
|
|
160
145
|
const tables = await Table.find({});
|
|
161
146
|
let id;
|
|
162
147
|
let form_action;
|
|
@@ -168,14 +153,13 @@ const triggerForm = async (req, trigger) => {
|
|
|
168
153
|
const hasChannel = Object.entries(getState().eventTypes)
|
|
169
154
|
.filter(([k, v]) => v.hasChannel)
|
|
170
155
|
.map(([k, v]) => k);
|
|
171
|
-
|
|
172
|
-
allActions.
|
|
156
|
+
|
|
157
|
+
const allActions = Trigger.action_options({ notRequireRow: false });
|
|
173
158
|
const table_triggers = ["Insert", "Update", "Delete", "Validate"];
|
|
174
159
|
const action_options = {};
|
|
175
|
-
const actionsNotRequiringRow =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
actionsNotRequiringRow.push("Multi-step action");
|
|
160
|
+
const actionsNotRequiringRow = Trigger.action_options({
|
|
161
|
+
notRequireRow: true,
|
|
162
|
+
});
|
|
179
163
|
|
|
180
164
|
Trigger.when_options.forEach((t) => {
|
|
181
165
|
if (table_triggers.includes(t)) action_options[t] = allActions;
|
|
@@ -237,6 +221,13 @@ const triggerForm = async (req, trigger) => {
|
|
|
237
221
|
showIf: { when_trigger: "Daily" },
|
|
238
222
|
sublabel: req.__("UTC timezone"),
|
|
239
223
|
},
|
|
224
|
+
{
|
|
225
|
+
name: "channel",
|
|
226
|
+
label: req.__("Time to run"),
|
|
227
|
+
input_type: "time_of_week",
|
|
228
|
+
showIf: { when_trigger: "Weekly" },
|
|
229
|
+
sublabel: req.__("UTC timezone"),
|
|
230
|
+
},
|
|
240
231
|
{
|
|
241
232
|
name: "channel",
|
|
242
233
|
label: req.__("Channel"),
|
|
@@ -555,7 +546,10 @@ router.get(
|
|
|
555
546
|
let trigger;
|
|
556
547
|
let id = parseInt(idorname);
|
|
557
548
|
if (id) trigger = await Trigger.findOne({ id });
|
|
558
|
-
else
|
|
549
|
+
else {
|
|
550
|
+
trigger = await Trigger.findOne({ name: idorname });
|
|
551
|
+
id = trigger.id;
|
|
552
|
+
}
|
|
559
553
|
|
|
560
554
|
if (!trigger) {
|
|
561
555
|
req.flash("warning", req.__("Action not found"));
|
|
@@ -761,7 +755,7 @@ router.post(
|
|
|
761
755
|
req.flash("success", req.__("Action configuration saved"));
|
|
762
756
|
res.redirect(
|
|
763
757
|
req.query.on_done_redirect &&
|
|
764
|
-
is_relative_url(req.query.on_done_redirect)
|
|
758
|
+
is_relative_url("/" + req.query.on_done_redirect)
|
|
765
759
|
? `/${req.query.on_done_redirect}`
|
|
766
760
|
: "/actions/"
|
|
767
761
|
);
|