@saltcorn/server 0.9.5-beta.6 → 0.9.5-beta.8
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/errors.js +1 -0
- package/help/Cordova Builder.tmd +13 -0
- package/load_plugins.js +71 -17
- package/locales/en.json +9 -2
- package/package.json +10 -9
- package/public/log_viewer_utils.js +17 -1
- package/public/saltcorn-common.js +14 -5
- package/public/saltcorn.js +47 -1
- package/routes/admin.js +147 -3
- package/routes/homepage.js +6 -3
- package/routes/plugins.js +8 -1
- package/routes/tables.js +4 -0
package/errors.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
The Cordova builder is a docker image with all dependencies to build Android apps.
|
|
2
|
+
It can be pulled from dockerhub while installing Saltcorn, or you can use the pull button.
|
|
3
|
+
|
|
4
|
+
Please make sure your server has a valid and accessible docker daemon running.
|
|
5
|
+
|
|
6
|
+
For this, either set up a standard docker installation
|
|
7
|
+
or use the [docker rootless mode](https://docs.docker.com/engine/security/rootless/) (recommended).
|
|
8
|
+
|
|
9
|
+
In a standard docker environment, you need root user privileges.
|
|
10
|
+
For this, you can add the user running Saltcorn to the docker group,
|
|
11
|
+
or if you already have the privileges, you are ready to go.
|
|
12
|
+
|
|
13
|
+
A docker daemon in rootless mode doesn't need any further configuration.
|
package/load_plugins.js
CHANGED
|
@@ -25,14 +25,29 @@ const loadPlugin = async (plugin, force) => {
|
|
|
25
25
|
typeof plugin.configuration === "string"
|
|
26
26
|
? JSON.parse(plugin.configuration)
|
|
27
27
|
: plugin.configuration;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
try {
|
|
29
|
+
// register plugin
|
|
30
|
+
getState().registerPlugin(
|
|
31
|
+
res.plugin_module.plugin_name || plugin.name,
|
|
32
|
+
res.plugin_module,
|
|
33
|
+
configuration,
|
|
34
|
+
res.location,
|
|
35
|
+
res.name
|
|
36
|
+
);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (force) {
|
|
39
|
+
// remove the install dir and try again
|
|
40
|
+
await loader.remove();
|
|
41
|
+
await loader.install(force);
|
|
42
|
+
getState().registerPlugin(
|
|
43
|
+
res.plugin_module.plugin_name || plugin.name,
|
|
44
|
+
res.plugin_module,
|
|
45
|
+
configuration,
|
|
46
|
+
res.location,
|
|
47
|
+
res.name
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
36
51
|
if (res.plugin_module.onLoad) {
|
|
37
52
|
try {
|
|
38
53
|
await res.plugin_module.onLoad(plugin.configuration);
|
|
@@ -76,9 +91,15 @@ const loadAllPlugins = async (force) => {
|
|
|
76
91
|
* @param plugin
|
|
77
92
|
* @param force
|
|
78
93
|
* @param noSignalOrDB
|
|
94
|
+
* @param __ translation function
|
|
79
95
|
* @returns {Promise<void>}
|
|
80
96
|
*/
|
|
81
|
-
const loadAndSaveNewPlugin = async (
|
|
97
|
+
const loadAndSaveNewPlugin = async (
|
|
98
|
+
plugin,
|
|
99
|
+
force,
|
|
100
|
+
noSignalOrDB,
|
|
101
|
+
__ = (str) => str
|
|
102
|
+
) => {
|
|
82
103
|
const tenants_unsafe_plugins = getRootState().getConfig(
|
|
83
104
|
"tenants_unsafe_plugins",
|
|
84
105
|
false
|
|
@@ -104,8 +125,10 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB) => {
|
|
|
104
125
|
return;
|
|
105
126
|
}
|
|
106
127
|
}
|
|
128
|
+
const msgs = [];
|
|
107
129
|
const loader = new PluginInstaller(plugin);
|
|
108
|
-
const { version, plugin_module, location } =
|
|
130
|
+
const { version, plugin_module, location, loadedWithReload } =
|
|
131
|
+
await loader.install(force);
|
|
109
132
|
|
|
110
133
|
// install dependecies
|
|
111
134
|
for (const loc of plugin_module.dependencies || []) {
|
|
@@ -118,13 +141,43 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB) => {
|
|
|
118
141
|
);
|
|
119
142
|
}
|
|
120
143
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
144
|
+
let registeredWithReload = false;
|
|
145
|
+
try {
|
|
146
|
+
getState().registerPlugin(
|
|
147
|
+
plugin_module.plugin_name || plugin.name,
|
|
148
|
+
plugin_module,
|
|
149
|
+
plugin.configuration,
|
|
150
|
+
location,
|
|
151
|
+
plugin.name
|
|
152
|
+
);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (force) {
|
|
155
|
+
getState().log(
|
|
156
|
+
2,
|
|
157
|
+
`Error registering plugin ${plugin.name}. Removing and trying again.`
|
|
158
|
+
);
|
|
159
|
+
await loader.remove();
|
|
160
|
+
await loader.install(force);
|
|
161
|
+
getState().registerPlugin(
|
|
162
|
+
plugin_module.plugin_name || plugin.name,
|
|
163
|
+
plugin_module,
|
|
164
|
+
plugin.configuration,
|
|
165
|
+
location,
|
|
166
|
+
plugin.name
|
|
167
|
+
);
|
|
168
|
+
registeredWithReload = true;
|
|
169
|
+
} else {
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (loadedWithReload || registeredWithReload) {
|
|
174
|
+
msgs.push(
|
|
175
|
+
__(
|
|
176
|
+
"The plugin was corrupted and had to be repaired. We recommend restarting your server.",
|
|
177
|
+
plugin.name
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
}
|
|
128
181
|
if (plugin_module.onLoad) {
|
|
129
182
|
try {
|
|
130
183
|
await plugin_module.onLoad(plugin.configuration);
|
|
@@ -141,6 +194,7 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB) => {
|
|
|
141
194
|
force: false, // okay ??
|
|
142
195
|
});
|
|
143
196
|
}
|
|
197
|
+
return msgs;
|
|
144
198
|
};
|
|
145
199
|
|
|
146
200
|
module.exports = {
|
package/locales/en.json
CHANGED
|
@@ -1384,5 +1384,12 @@
|
|
|
1384
1384
|
"Optionally associate a table with this trigger": "Optionally associate a table with this trigger",
|
|
1385
1385
|
"Delete table": "Delete table",
|
|
1386
1386
|
"Signup role": "Signup role",
|
|
1387
|
-
"The initial role of signed up users": "The initial role of signed up users"
|
|
1388
|
-
|
|
1387
|
+
"The initial role of signed up users": "The initial role of signed up users",
|
|
1388
|
+
"Cordova builder": "Cordova builder",
|
|
1389
|
+
"not available": "not available",
|
|
1390
|
+
"pull": "pull",
|
|
1391
|
+
"refresh": "refresh",
|
|
1392
|
+
"installed": "installed",
|
|
1393
|
+
"Include table history in backup": "Include table history in backup",
|
|
1394
|
+
"The plugin was corrupted and had to be repaired. We recommend restarting your server.": "The plugin was corrupted and had to be repaired. We recommend restarting your server."
|
|
1395
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/server",
|
|
3
|
-
"version": "0.9.5-beta.
|
|
3
|
+
"version": "0.9.5-beta.8",
|
|
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
9
|
"@aws-sdk/client-s3": "^3.451.0",
|
|
10
|
-
"@saltcorn/base-plugin": "0.9.5-beta.
|
|
11
|
-
"@saltcorn/builder": "0.9.5-beta.
|
|
12
|
-
"@saltcorn/data": "0.9.5-beta.
|
|
13
|
-
"@saltcorn/admin-models": "0.9.5-beta.
|
|
14
|
-
"@saltcorn/filemanager": "0.9.5-beta.
|
|
15
|
-
"@saltcorn/markup": "0.9.5-beta.
|
|
16
|
-
"@saltcorn/plugins-loader": "0.9.5-beta.
|
|
17
|
-
"@saltcorn/sbadmin2": "0.9.5-beta.
|
|
10
|
+
"@saltcorn/base-plugin": "0.9.5-beta.8",
|
|
11
|
+
"@saltcorn/builder": "0.9.5-beta.8",
|
|
12
|
+
"@saltcorn/data": "0.9.5-beta.8",
|
|
13
|
+
"@saltcorn/admin-models": "0.9.5-beta.8",
|
|
14
|
+
"@saltcorn/filemanager": "0.9.5-beta.8",
|
|
15
|
+
"@saltcorn/markup": "0.9.5-beta.8",
|
|
16
|
+
"@saltcorn/plugins-loader": "0.9.5-beta.8",
|
|
17
|
+
"@saltcorn/sbadmin2": "0.9.5-beta.8",
|
|
18
18
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
19
19
|
"@socket.io/sticky": "^1.0.1",
|
|
20
20
|
"adm-zip": "0.5.10",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"cors": "2.8.5",
|
|
28
28
|
"csurf": "^1.11.0",
|
|
29
29
|
"csv-stringify": "^5.5.0",
|
|
30
|
+
"dockerode": "~4.0.2",
|
|
30
31
|
"express": "^4.17.1",
|
|
31
32
|
"express-fileupload": "^1.1.8",
|
|
32
33
|
"express-promise-router": "^3.0.3",
|
|
@@ -131,7 +131,23 @@ var logViewerHelpers = (() => {
|
|
|
131
131
|
|
|
132
132
|
return {
|
|
133
133
|
init_log_socket: () => {
|
|
134
|
-
|
|
134
|
+
let socket = null;
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
if (socket === null || socket.disconnected) {
|
|
137
|
+
notifyAlert({
|
|
138
|
+
type: "danger",
|
|
139
|
+
text: "Unable to connect to the server",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}, 5000);
|
|
143
|
+
try {
|
|
144
|
+
socket = io({ transports: ["websocket"] });
|
|
145
|
+
} catch (e) {
|
|
146
|
+
notifyAlert({
|
|
147
|
+
type: "danger",
|
|
148
|
+
text: "Unable to connect to the server " + e.message,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
135
151
|
startTrackingMsg();
|
|
136
152
|
socket.on("connect", () => handleConnect(socket));
|
|
137
153
|
socket.on("disconnect", handleDisconnect);
|
|
@@ -76,7 +76,7 @@ function valid_js_var_name(s) {
|
|
|
76
76
|
return !!s.match(/^[a-zA-Z_$][a-zA-Z_$0-9]*$/);
|
|
77
77
|
}
|
|
78
78
|
function apply_showif() {
|
|
79
|
-
const isNode =
|
|
79
|
+
const isNode = getIsNode();
|
|
80
80
|
$("[data-show-if]").each(function (ix, element) {
|
|
81
81
|
var e = $(element);
|
|
82
82
|
try {
|
|
@@ -571,7 +571,7 @@ function reload_on_init() {
|
|
|
571
571
|
}
|
|
572
572
|
function initialize_page() {
|
|
573
573
|
if (window._sc_locale && window.dayjs) dayjs.locale(window._sc_locale);
|
|
574
|
-
const isNode =
|
|
574
|
+
const isNode = getIsNode();
|
|
575
575
|
//console.log("init page");
|
|
576
576
|
$(".blur-on-enter-keypress").bind("keyup", function (e) {
|
|
577
577
|
if (e.keyCode === 13) e.target.blur();
|
|
@@ -877,7 +877,7 @@ function initialize_page() {
|
|
|
877
877
|
$(initialize_page);
|
|
878
878
|
|
|
879
879
|
function cancel_inline_edit(e, opts1) {
|
|
880
|
-
const isNode =
|
|
880
|
+
const isNode = getIsNode();
|
|
881
881
|
var opts = JSON.parse(decodeURIComponent(opts1 || "") || "{}");
|
|
882
882
|
var form = $(e.target).closest("form");
|
|
883
883
|
form.replaceWith(opts.resetHtml);
|
|
@@ -885,7 +885,7 @@ function cancel_inline_edit(e, opts1) {
|
|
|
885
885
|
}
|
|
886
886
|
|
|
887
887
|
function inline_submit_success(e, form, opts) {
|
|
888
|
-
const isNode =
|
|
888
|
+
const isNode = getIsNode();
|
|
889
889
|
const formDataArray = form.serializeArray();
|
|
890
890
|
if (opts) {
|
|
891
891
|
let fdEntry = formDataArray.find((f) => f.name == opts.key);
|
|
@@ -1025,6 +1025,15 @@ function tristateClick(e, required) {
|
|
|
1025
1025
|
}
|
|
1026
1026
|
}
|
|
1027
1027
|
|
|
1028
|
+
function getIsNode() {
|
|
1029
|
+
try {
|
|
1030
|
+
return typeof parent?.saltcorn?.data?.state === "undefined";
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
//probably in an iframe
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1028
1037
|
function buildToast(txt, type, spin) {
|
|
1029
1038
|
const realtype = type === "error" ? "danger" : type;
|
|
1030
1039
|
const icon =
|
|
@@ -1035,7 +1044,7 @@ function buildToast(txt, type, spin) {
|
|
|
1035
1044
|
: realtype === "warning"
|
|
1036
1045
|
? "fa-exclamation-triangle"
|
|
1037
1046
|
: "";
|
|
1038
|
-
const isNode =
|
|
1047
|
+
const isNode = getIsNode();
|
|
1039
1048
|
const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
|
|
1040
1049
|
return {
|
|
1041
1050
|
id: rndid,
|
package/public/saltcorn.js
CHANGED
|
@@ -821,6 +821,17 @@ function build_mobile_app(button) {
|
|
|
821
821
|
params.includedPlugins = Array.from(pluginsSelect.options)
|
|
822
822
|
.filter((option) => !option.hidden)
|
|
823
823
|
.map((option) => option.value);
|
|
824
|
+
|
|
825
|
+
if (
|
|
826
|
+
params.useDocker &&
|
|
827
|
+
!cordovaBuilderAvailable &&
|
|
828
|
+
!confirm(
|
|
829
|
+
"Docker is selected but the Cordova builder seems not to be installed. " +
|
|
830
|
+
"Do you really want to continue?"
|
|
831
|
+
)
|
|
832
|
+
) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
824
835
|
ajax_post("/admin/build-mobile-app", {
|
|
825
836
|
data: params,
|
|
826
837
|
success: (data) => {
|
|
@@ -834,6 +845,41 @@ function build_mobile_app(button) {
|
|
|
834
845
|
});
|
|
835
846
|
}
|
|
836
847
|
|
|
848
|
+
function pull_cordova_builder() {
|
|
849
|
+
ajax_post("/admin/mobile-app/pull-cordova-builder", {
|
|
850
|
+
success: () => {
|
|
851
|
+
notifyAlert(
|
|
852
|
+
"Pulling the the cordova-builder. " +
|
|
853
|
+
"To see the progress, open the logs viewer with the System logging verbosity set to 'All'."
|
|
854
|
+
);
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function check_cordova_builder() {
|
|
860
|
+
$.ajax("/admin/mobile-app/check-cordova-builder", {
|
|
861
|
+
type: "GET",
|
|
862
|
+
success: function (res) {
|
|
863
|
+
cordovaBuilderAvailable = !!res.installed;
|
|
864
|
+
if (cordovaBuilderAvailable) {
|
|
865
|
+
$("#dockerBuilderStatusId").html(
|
|
866
|
+
`<span>
|
|
867
|
+
installed<i class="ps-2 fas fa-check text-success"></i>
|
|
868
|
+
</span>
|
|
869
|
+
`
|
|
870
|
+
);
|
|
871
|
+
} else {
|
|
872
|
+
$("#dockerBuilderStatusId").html(
|
|
873
|
+
`<span>
|
|
874
|
+
not available<i class="ps-2 fas fa-times text-danger"></i>
|
|
875
|
+
</span>
|
|
876
|
+
`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
|
|
837
883
|
function move_to_synched() {
|
|
838
884
|
const opts = $("#unsynched-tbls-select-id");
|
|
839
885
|
$("#synched-tbls-select-id").removeAttr("selected");
|
|
@@ -897,7 +943,7 @@ function toggle_tbl_sync() {
|
|
|
897
943
|
function toggle_android_platform() {
|
|
898
944
|
if ($("#androidCheckboxId")[0].checked === true) {
|
|
899
945
|
$("#dockerCheckboxId").attr("hidden", false);
|
|
900
|
-
$("#dockerCheckboxId").attr("checked",
|
|
946
|
+
$("#dockerCheckboxId").attr("checked", cordovaBuilderAvailable);
|
|
901
947
|
$("#dockerLabelId").removeClass("d-none");
|
|
902
948
|
} else {
|
|
903
949
|
$("#dockerCheckboxId").attr("hidden", true);
|
package/routes/admin.js
CHANGED
|
@@ -107,6 +107,7 @@ const { getSafeSaltcornCmd } = require("@saltcorn/data/utils");
|
|
|
107
107
|
const stream = require("stream");
|
|
108
108
|
const Crash = require("@saltcorn/data/models/crash");
|
|
109
109
|
const { get_help_markup } = require("../help/index.js");
|
|
110
|
+
const Docker = require("dockerode");
|
|
110
111
|
|
|
111
112
|
const router = new Router();
|
|
112
113
|
module.exports = router;
|
|
@@ -273,6 +274,8 @@ router.get(
|
|
|
273
274
|
const aBackupFilePrefixForm = backupFilePrefixForm(req);
|
|
274
275
|
aBackupFilePrefixForm.values.backup_file_prefix =
|
|
275
276
|
getState().getConfig("backup_file_prefix");
|
|
277
|
+
aBackupFilePrefixForm.values.backup_history =
|
|
278
|
+
getState().getConfig("backup_history");
|
|
276
279
|
//
|
|
277
280
|
const backupForm = autoBackupForm(req);
|
|
278
281
|
backupForm.values.auto_backup_frequency = getState().getConfig(
|
|
@@ -673,6 +676,13 @@ const backupFilePrefixForm = (req) =>
|
|
|
673
676
|
sublabel: req.__("Backup file prefix"),
|
|
674
677
|
default: "sc-backup-",
|
|
675
678
|
},
|
|
679
|
+
{
|
|
680
|
+
type: "Bool",
|
|
681
|
+
label: req.__("History"),
|
|
682
|
+
name: "backup_history",
|
|
683
|
+
sublabel: req.__("Include table history in backup"),
|
|
684
|
+
default: true,
|
|
685
|
+
},
|
|
676
686
|
],
|
|
677
687
|
});
|
|
678
688
|
|
|
@@ -1104,6 +1114,28 @@ router.post(
|
|
|
1104
1114
|
})
|
|
1105
1115
|
);
|
|
1106
1116
|
|
|
1117
|
+
const pullCordovaBuilder = (req, res) => {
|
|
1118
|
+
const child = spawn("docker", ["pull", "saltcorn/cordova-builder"], {
|
|
1119
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1120
|
+
});
|
|
1121
|
+
return new Promise((resolve, reject) => {
|
|
1122
|
+
child.stdout.on("data", (data) => {
|
|
1123
|
+
res.write(data);
|
|
1124
|
+
});
|
|
1125
|
+
child.stderr?.on("data", (data) => {
|
|
1126
|
+
res.write(data);
|
|
1127
|
+
});
|
|
1128
|
+
child.on("exit", function (code, signal) {
|
|
1129
|
+
resolve(code);
|
|
1130
|
+
});
|
|
1131
|
+
child.on("error", (msg) => {
|
|
1132
|
+
const message = msg.message ? msg.message : msg.code;
|
|
1133
|
+
res.write(req.__("Error: ") + message + "\n");
|
|
1134
|
+
resolve(msg.code);
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1107
1139
|
/**
|
|
1108
1140
|
* Do Upgrade
|
|
1109
1141
|
* @name post/upgrade
|
|
@@ -1132,7 +1164,14 @@ router.post(
|
|
|
1132
1164
|
child.stderr?.on("data", (data) => {
|
|
1133
1165
|
res.write(data);
|
|
1134
1166
|
});
|
|
1135
|
-
child.on("exit", function (code, signal) {
|
|
1167
|
+
child.on("exit", async function (code, signal) {
|
|
1168
|
+
if (code === 0) {
|
|
1169
|
+
res.write(
|
|
1170
|
+
req.__("Pulling the cordova-builder docker image...") + "\n"
|
|
1171
|
+
);
|
|
1172
|
+
const pullCode = await pullCordovaBuilder(req, res);
|
|
1173
|
+
res.write(req.__("Pull done with code %s", pullCode) + "\n");
|
|
1174
|
+
}
|
|
1136
1175
|
res.end(
|
|
1137
1176
|
req.__(
|
|
1138
1177
|
`Upgrade done (if it was available) with code ${code}.\n\nPress the BACK button in your browser, then RELOAD the page.`
|
|
@@ -1481,8 +1520,9 @@ router.get(
|
|
|
1481
1520
|
});
|
|
1482
1521
|
})
|
|
1483
1522
|
);
|
|
1484
|
-
const buildDialogScript = () => {
|
|
1523
|
+
const buildDialogScript = (cordovaBuilderAvailable) => {
|
|
1485
1524
|
return `<script>
|
|
1525
|
+
var cordovaBuilderAvailable = ${cordovaBuilderAvailable};
|
|
1486
1526
|
function showEntrySelect(type) {
|
|
1487
1527
|
for( const currentType of ["view", "page", "pagegroup"]) {
|
|
1488
1528
|
const tab = $('#' + currentType + 'NavLinkID');
|
|
@@ -1519,6 +1559,17 @@ const buildDialogScript = () => {
|
|
|
1519
1559
|
}
|
|
1520
1560
|
</script>`;
|
|
1521
1561
|
};
|
|
1562
|
+
|
|
1563
|
+
const imageAvailable = async () => {
|
|
1564
|
+
try {
|
|
1565
|
+
const image = new Docker().getImage("saltcorn/cordova-builder");
|
|
1566
|
+
await image.inspect();
|
|
1567
|
+
return true;
|
|
1568
|
+
} catch (e) {
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1522
1573
|
/**
|
|
1523
1574
|
* Build mobile app
|
|
1524
1575
|
*/
|
|
@@ -1538,13 +1589,14 @@ router.get(
|
|
|
1538
1589
|
);
|
|
1539
1590
|
const builderSettings =
|
|
1540
1591
|
getState().getConfig("mobile_builder_settings") || {};
|
|
1592
|
+
const dockerAvailable = await imageAvailable();
|
|
1541
1593
|
send_admin_page({
|
|
1542
1594
|
res,
|
|
1543
1595
|
req,
|
|
1544
1596
|
active_sub: "Mobile app",
|
|
1545
1597
|
headers: [
|
|
1546
1598
|
{
|
|
1547
|
-
headerTag: buildDialogScript(),
|
|
1599
|
+
headerTag: buildDialogScript(dockerAvailable),
|
|
1548
1600
|
},
|
|
1549
1601
|
],
|
|
1550
1602
|
contents: {
|
|
@@ -2165,6 +2217,56 @@ router.get(
|
|
|
2165
2217
|
)
|
|
2166
2218
|
)
|
|
2167
2219
|
)
|
|
2220
|
+
),
|
|
2221
|
+
div(
|
|
2222
|
+
{ class: "row pb-3 pt-3" },
|
|
2223
|
+
div(
|
|
2224
|
+
label(
|
|
2225
|
+
{ class: "form-label fw-bold" },
|
|
2226
|
+
req.__("Cordova builder") +
|
|
2227
|
+
a(
|
|
2228
|
+
{
|
|
2229
|
+
href: "javascript:ajax_modal('/admin/help/Cordova Builder?')",
|
|
2230
|
+
},
|
|
2231
|
+
i({ class: "fas fa-question-circle ps-1" })
|
|
2232
|
+
)
|
|
2233
|
+
)
|
|
2234
|
+
),
|
|
2235
|
+
div(
|
|
2236
|
+
{ class: "col-sm-4" },
|
|
2237
|
+
div(
|
|
2238
|
+
{
|
|
2239
|
+
id: "dockerBuilderStatusId",
|
|
2240
|
+
class: "",
|
|
2241
|
+
},
|
|
2242
|
+
dockerAvailable
|
|
2243
|
+
? span(
|
|
2244
|
+
req.__("installed"),
|
|
2245
|
+
i({ class: "ps-2 fas fa-check text-success" })
|
|
2246
|
+
)
|
|
2247
|
+
: span(
|
|
2248
|
+
req.__("not available"),
|
|
2249
|
+
i({ class: "ps-2 fas fa-times text-danger" })
|
|
2250
|
+
)
|
|
2251
|
+
)
|
|
2252
|
+
),
|
|
2253
|
+
div(
|
|
2254
|
+
{ class: "col-sm-4" },
|
|
2255
|
+
button(
|
|
2256
|
+
{
|
|
2257
|
+
id: "pullCordovaBtnId",
|
|
2258
|
+
type: "button",
|
|
2259
|
+
onClick: `pull_cordova_builder(this);`,
|
|
2260
|
+
class: "btn btn-warning",
|
|
2261
|
+
},
|
|
2262
|
+
req.__("pull")
|
|
2263
|
+
),
|
|
2264
|
+
span(
|
|
2265
|
+
{ role: "button", onClick: "check_cordova_builder()" },
|
|
2266
|
+
span({ class: "ps-3" }, req.__("refresh")),
|
|
2267
|
+
i({ class: "ps-2 fas fa-undo" })
|
|
2268
|
+
)
|
|
2269
|
+
)
|
|
2168
2270
|
)
|
|
2169
2271
|
),
|
|
2170
2272
|
button(
|
|
@@ -2419,6 +2521,48 @@ router.post(
|
|
|
2419
2521
|
})
|
|
2420
2522
|
);
|
|
2421
2523
|
|
|
2524
|
+
router.post(
|
|
2525
|
+
"/mobile-app/pull-cordova-builder",
|
|
2526
|
+
isAdmin,
|
|
2527
|
+
error_catcher(async (req, res) => {
|
|
2528
|
+
const state = getState();
|
|
2529
|
+
const child = spawn(
|
|
2530
|
+
"docker",
|
|
2531
|
+
["image", "pull", "saltcorn/cordova-builder:latest"],
|
|
2532
|
+
{
|
|
2533
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2534
|
+
cwd: ".",
|
|
2535
|
+
}
|
|
2536
|
+
);
|
|
2537
|
+
child.stdout.on("data", (data) => {
|
|
2538
|
+
state.log(5, data.toString());
|
|
2539
|
+
});
|
|
2540
|
+
child.stderr.on("data", (data) => {
|
|
2541
|
+
state.log(1, data.toString());
|
|
2542
|
+
});
|
|
2543
|
+
child.on("exit", (exitCode, signal) => {
|
|
2544
|
+
state.log(
|
|
2545
|
+
2,
|
|
2546
|
+
`"pull cordova-builder exit with code: ${exitCode} and signal: ${signal}`
|
|
2547
|
+
);
|
|
2548
|
+
});
|
|
2549
|
+
child.on("error", (msg) => {
|
|
2550
|
+
state.log(1, `pull cordova-builder error: ${msg}`);
|
|
2551
|
+
});
|
|
2552
|
+
|
|
2553
|
+
res.json({});
|
|
2554
|
+
})
|
|
2555
|
+
);
|
|
2556
|
+
|
|
2557
|
+
router.get(
|
|
2558
|
+
"/mobile-app/check-cordova-builder",
|
|
2559
|
+
isAdmin,
|
|
2560
|
+
error_catcher(async (req, res) => {
|
|
2561
|
+
const installed = await imageAvailable();
|
|
2562
|
+
res.json({ installed });
|
|
2563
|
+
})
|
|
2564
|
+
);
|
|
2565
|
+
|
|
2422
2566
|
/**
|
|
2423
2567
|
* Do Clear All
|
|
2424
2568
|
* @function
|
package/routes/homepage.js
CHANGED
|
@@ -547,7 +547,7 @@ const no_views_logged_in = async (req, res) => {
|
|
|
547
547
|
* @returns {Promise<boolean>}
|
|
548
548
|
*/
|
|
549
549
|
const get_config_response = async (role_id, res, req) => {
|
|
550
|
-
const wrap = async (contents, homeCfg, title, description) => {
|
|
550
|
+
const wrap = async (contents, homeCfg, title, description, no_menu) => {
|
|
551
551
|
if (contents.html_file) await sendHtmlFile(req, res, contents.html_file);
|
|
552
552
|
else
|
|
553
553
|
res.sendWrap(
|
|
@@ -555,6 +555,7 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
555
555
|
title: title || "",
|
|
556
556
|
description: description || "",
|
|
557
557
|
bodyClass: "page_" + db.sqlsanitize(homeCfg),
|
|
558
|
+
no_menu,
|
|
558
559
|
},
|
|
559
560
|
contents
|
|
560
561
|
);
|
|
@@ -574,7 +575,8 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
574
575
|
await db_page.run(req.query, { res, req }),
|
|
575
576
|
homeCfg,
|
|
576
577
|
db_page.title,
|
|
577
|
-
db_page.description
|
|
578
|
+
db_page.description,
|
|
579
|
+
db_page.attributes?.no_menu
|
|
578
580
|
);
|
|
579
581
|
else {
|
|
580
582
|
const group = PageGroup.findOne({ name: homeCfg });
|
|
@@ -587,7 +589,8 @@ const get_config_response = async (role_id, res, req) => {
|
|
|
587
589
|
await eligible.run(req.query, { res, req }),
|
|
588
590
|
homeCfg,
|
|
589
591
|
eligible.title,
|
|
590
|
-
eligible.description
|
|
592
|
+
eligible.description,
|
|
593
|
+
eligible.attributes?.no_menu
|
|
591
594
|
);
|
|
592
595
|
} else wrap(req.__("%s has no eligible page", group.name), homeCfg);
|
|
593
596
|
} else res.redirect(homeCfg);
|
package/routes/plugins.js
CHANGED
|
@@ -1243,7 +1243,12 @@ router.post(
|
|
|
1243
1243
|
res.redirect(`/plugins`);
|
|
1244
1244
|
return;
|
|
1245
1245
|
}
|
|
1246
|
-
await load_plugins.loadAndSaveNewPlugin(
|
|
1246
|
+
const msgs = await load_plugins.loadAndSaveNewPlugin(
|
|
1247
|
+
plugin,
|
|
1248
|
+
forceReInstall,
|
|
1249
|
+
undefined,
|
|
1250
|
+
req.__
|
|
1251
|
+
);
|
|
1247
1252
|
const plugin_module = getState().plugins[name];
|
|
1248
1253
|
await sleep(1000); // Allow other workers to load this plugin
|
|
1249
1254
|
await getState().refresh_views();
|
|
@@ -1257,9 +1262,11 @@ router.post(
|
|
|
1257
1262
|
plugin_db.name
|
|
1258
1263
|
)
|
|
1259
1264
|
);
|
|
1265
|
+
if (msgs?.length > 0) req.flash("warning", msgs.join("<br>"));
|
|
1260
1266
|
res.redirect(`/plugins/configure/${plugin_db.name}`);
|
|
1261
1267
|
} else {
|
|
1262
1268
|
req.flash("success", req.__(`Module %s installed`, plugin.name));
|
|
1269
|
+
if (msgs?.length > 0) req.flash("warning", msgs.join("<br>"));
|
|
1263
1270
|
res.redirect(`/plugins`);
|
|
1264
1271
|
}
|
|
1265
1272
|
})
|
package/routes/tables.js
CHANGED
|
@@ -167,6 +167,10 @@ const tableForm = async (table, req) => {
|
|
|
167
167
|
label: req.__("Version history"),
|
|
168
168
|
sublabel: req.__("Track table data changes over time"),
|
|
169
169
|
name: "versioned",
|
|
170
|
+
attributes: {
|
|
171
|
+
onChange:
|
|
172
|
+
"if(!this.checked && !confirm('Are you sure? This will delete all history')) {this.checked = true; return false}",
|
|
173
|
+
},
|
|
170
174
|
type: "Bool",
|
|
171
175
|
},
|
|
172
176
|
...(table.name === "users"
|