@saltcorn/server 1.0.0-beta.9 → 1.0.0-rc.2
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 +2 -0
- package/load_plugins.js +89 -8
- package/locales/en.json +13 -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 +195 -100
- package/public/saltcorn.css +17 -0
- package/public/saltcorn.js +76 -17
- package/routes/actions.js +19 -25
- package/routes/admin.js +256 -54
- package/routes/delete.js +4 -1
- package/routes/eventlog.js +2 -1
- package/routes/fields.js +21 -5
- package/routes/files.js +3 -0
- 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 +118 -23
- package/routes/scapi.js +19 -0
- package/routes/search.js +6 -1
- package/routes/sync.js +2 -1
- package/routes/tables.js +14 -4
- package/routes/viewedit.js +7 -4
- package/serve.js +17 -3
- 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() {
|
|
@@ -451,7 +457,8 @@ function saveAndContinue(e, k, event) {
|
|
|
451
457
|
},
|
|
452
458
|
error: function (request) {
|
|
453
459
|
var ct = request.getResponseHeader("content-type") || "";
|
|
454
|
-
if (
|
|
460
|
+
if (checkNetworkError(request)) {
|
|
461
|
+
} else if (ct.startsWith && ct.startsWith("application/json")) {
|
|
455
462
|
notifyAlert({ type: "danger", text: request.responseJSON.error });
|
|
456
463
|
} else {
|
|
457
464
|
$("#page-inner-content").html(request.responseText);
|
|
@@ -500,6 +507,7 @@ function applyViewConfig(e, url, k, event) {
|
|
|
500
507
|
},
|
|
501
508
|
data: JSON.stringify(cfg),
|
|
502
509
|
error: function (request) {
|
|
510
|
+
checkNetworkError(request);
|
|
503
511
|
window.savingViewConfig = false;
|
|
504
512
|
ajax_indicate_error(e, request);
|
|
505
513
|
},
|
|
@@ -578,6 +586,7 @@ function ajaxSubmitForm(e, force_no_reload) {
|
|
|
578
586
|
else common_done(res, form.attr("data-viewname"));
|
|
579
587
|
},
|
|
580
588
|
error: function (request) {
|
|
589
|
+
checkNetworkError(request);
|
|
581
590
|
var title = request.getResponseHeader("Page-Title");
|
|
582
591
|
if (title) $("#scmodal .modal-title").html(decodeURIComponent(title));
|
|
583
592
|
var body = request.responseText;
|
|
@@ -594,6 +603,9 @@ function ajax_post_json(url, data, args = {}) {
|
|
|
594
603
|
...args,
|
|
595
604
|
});
|
|
596
605
|
}
|
|
606
|
+
|
|
607
|
+
let scNetworkErrorSignaled = false;
|
|
608
|
+
|
|
597
609
|
function ajax_post(url, args) {
|
|
598
610
|
$.ajax(url, {
|
|
599
611
|
type: "POST",
|
|
@@ -603,10 +615,31 @@ function ajax_post(url, args) {
|
|
|
603
615
|
...(args || {}),
|
|
604
616
|
})
|
|
605
617
|
.done(ajax_done)
|
|
606
|
-
.fail((e) =>
|
|
607
|
-
|
|
608
|
-
|
|
618
|
+
.fail((e, ...more) => {
|
|
619
|
+
if (!checkNetworkError(e))
|
|
620
|
+
return ajax_done(
|
|
621
|
+
e.responseJSON || { error: "Unknown error: " + e.responseText }
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function checkNetworkError(e) {
|
|
627
|
+
if (e.readyState == 0 && !e.responseText && !e.responseJSON) {
|
|
628
|
+
//network error
|
|
629
|
+
if (scNetworkErrorSignaled) return true;
|
|
630
|
+
scNetworkErrorSignaled = true;
|
|
631
|
+
setTimeout(() => {
|
|
632
|
+
scNetworkErrorSignaled = false;
|
|
633
|
+
}, 1000);
|
|
634
|
+
console.error("Network error", e);
|
|
635
|
+
notifyAlert({
|
|
636
|
+
type: "danger",
|
|
637
|
+
text: "Network connection error",
|
|
638
|
+
});
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
609
641
|
}
|
|
642
|
+
|
|
610
643
|
function ajax_post_btn(e, reload_on_done, reload_delay) {
|
|
611
644
|
var form = $(e).closest("form");
|
|
612
645
|
var url = form.attr("action");
|
|
@@ -620,6 +653,7 @@ function ajax_post_btn(e, reload_on_done, reload_delay) {
|
|
|
620
653
|
success: function () {
|
|
621
654
|
if (reload_on_done) location.reload();
|
|
622
655
|
},
|
|
656
|
+
error: checkNetworkError,
|
|
623
657
|
complete: function () {
|
|
624
658
|
if (reload_delay)
|
|
625
659
|
setTimeout(function () {
|
|
@@ -641,6 +675,7 @@ function api_action_call(name, body) {
|
|
|
641
675
|
success: function (res) {
|
|
642
676
|
common_done(res.data);
|
|
643
677
|
},
|
|
678
|
+
error: checkNetworkError,
|
|
644
679
|
});
|
|
645
680
|
}
|
|
646
681
|
|
|
@@ -840,7 +875,7 @@ function build_mobile_app(button) {
|
|
|
840
875
|
|
|
841
876
|
if (
|
|
842
877
|
params.useDocker &&
|
|
843
|
-
!cordovaBuilderAvailable &&
|
|
878
|
+
!window.cordovaBuilderAvailable &&
|
|
844
879
|
!confirm(
|
|
845
880
|
"Docker is selected but the Cordova builder seems not to be installed. " +
|
|
846
881
|
"Do you really want to continue?"
|
|
@@ -848,6 +883,30 @@ function build_mobile_app(button) {
|
|
|
848
883
|
) {
|
|
849
884
|
return;
|
|
850
885
|
}
|
|
886
|
+
if (
|
|
887
|
+
isSbadmin2 &&
|
|
888
|
+
!confirm(
|
|
889
|
+
"It seems you are using the standard sbadmin2 layout. " +
|
|
890
|
+
"This layout is not optimized for mobile, consider using any-bootstrap-theme. " +
|
|
891
|
+
"Do you really want to continue?"
|
|
892
|
+
)
|
|
893
|
+
) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const notSupportedPlugins = params.includedPlugins.filter(
|
|
897
|
+
(plugin) => !window.pluginsReadyForMobile.includes(plugin)
|
|
898
|
+
);
|
|
899
|
+
if (
|
|
900
|
+
notSupportedPlugins.length > 0 &&
|
|
901
|
+
!confirm(
|
|
902
|
+
`It seems that the plugins '${notSupportedPlugins.join(
|
|
903
|
+
", "
|
|
904
|
+
)}' are not ready for mobile. Do you really want to continue?`
|
|
905
|
+
)
|
|
906
|
+
) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
|
|
851
910
|
ajax_post("/admin/build-mobile-app", {
|
|
852
911
|
data: params,
|
|
853
912
|
success: (data) => {
|
|
@@ -919,8 +978,8 @@ function check_cordova_builder() {
|
|
|
919
978
|
$.ajax("/admin/mobile-app/check-cordova-builder", {
|
|
920
979
|
type: "GET",
|
|
921
980
|
success: function (res) {
|
|
922
|
-
cordovaBuilderAvailable = !!res.installed;
|
|
923
|
-
if (cordovaBuilderAvailable) {
|
|
981
|
+
window.cordovaBuilderAvailable = !!res.installed;
|
|
982
|
+
if (window.cordovaBuilderAvailable) {
|
|
924
983
|
$("#dockerBuilderStatusId").html(
|
|
925
984
|
`<span>
|
|
926
985
|
installed<i class="ps-2 fas fa-check text-success"></i>
|
|
@@ -1014,7 +1073,7 @@ function toggle_tbl_sync() {
|
|
|
1014
1073
|
function toggle_android_platform() {
|
|
1015
1074
|
if ($("#androidCheckboxId")[0].checked === true) {
|
|
1016
1075
|
$("#dockerCheckboxId").attr("hidden", false);
|
|
1017
|
-
$("#dockerCheckboxId").attr("checked", cordovaBuilderAvailable);
|
|
1076
|
+
$("#dockerCheckboxId").attr("checked", window.cordovaBuilderAvailable);
|
|
1018
1077
|
$("#dockerLabelId").removeClass("d-none");
|
|
1019
1078
|
} else {
|
|
1020
1079
|
$("#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
|
);
|