@saltcorn/server 0.9.5-beta.1 → 0.9.5-beta.11
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/auth/admin.js +7 -0
- package/auth/routes.js +7 -3
- package/errors.js +1 -0
- package/help/Cordova Builder.tmd +13 -0
- package/load_plugins.js +83 -144
- package/locales/en.json +9 -2
- package/package.json +16 -13
- package/public/flatpickr.min.js +2 -2
- package/public/log_viewer_utils.js +17 -1
- package/public/saltcorn-common.js +33 -11
- package/public/saltcorn.js +51 -3
- package/restart_watcher.js +1 -0
- package/routes/admin.js +149 -3
- package/routes/common_lists.js +1 -1
- package/routes/fields.js +9 -7
- package/routes/homepage.js +6 -3
- package/routes/pageedit.js +1 -1
- package/routes/plugins.js +10 -1
- package/routes/tables.js +4 -0
- package/serve.js +1 -1
- package/tests/page.test.js +11 -1
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
|
|
@@ -2511,6 +2655,8 @@ router.post(
|
|
|
2511
2655
|
await getState().refresh();
|
|
2512
2656
|
}
|
|
2513
2657
|
if (form.values.users) {
|
|
2658
|
+
await db.deleteWhere("_sc_notifications");
|
|
2659
|
+
|
|
2514
2660
|
const users1 = Table.findOne({ name: "users" });
|
|
2515
2661
|
const userfields1 = await users1.getFields();
|
|
2516
2662
|
|
package/routes/common_lists.js
CHANGED
package/routes/fields.js
CHANGED
|
@@ -994,13 +994,15 @@ router.post(
|
|
|
994
994
|
if (oldRow) {
|
|
995
995
|
const value = oldRow[kpath[kpath.length - 1]];
|
|
996
996
|
//TODO run fieldview
|
|
997
|
-
res.send(
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
997
|
+
if (value === null || typeof value === "undefined") res.send("");
|
|
998
|
+
else
|
|
999
|
+
res.send(
|
|
1000
|
+
typeof value === "string"
|
|
1001
|
+
? value
|
|
1002
|
+
: value?.toString
|
|
1003
|
+
? value.toString()
|
|
1004
|
+
: `${value}`
|
|
1005
|
+
);
|
|
1004
1006
|
return;
|
|
1005
1007
|
}
|
|
1006
1008
|
}
|
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/pageedit.js
CHANGED
|
@@ -196,7 +196,7 @@ const pageBuilderData = async (req, context) => {
|
|
|
196
196
|
f.required = false;
|
|
197
197
|
if (f.type && f.type.name === "Bool") f.fieldview = "tristate";
|
|
198
198
|
|
|
199
|
-
await f.fill_fkey_options(true);
|
|
199
|
+
//await f.fill_fkey_options(true);
|
|
200
200
|
fixed_state_fields[view.name].push(f);
|
|
201
201
|
if (table.name === "users" && f.primary_key)
|
|
202
202
|
fixed_state_fields[view.name].push(
|
package/routes/plugins.js
CHANGED
|
@@ -57,6 +57,7 @@ const { flash_restart } = require("../markup/admin.js");
|
|
|
57
57
|
const { sleep, removeNonWordChars } = require("@saltcorn/data/utils");
|
|
58
58
|
const { loadAllPlugins } = require("../load_plugins");
|
|
59
59
|
const npmFetch = require("npm-registry-fetch");
|
|
60
|
+
const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
63
|
* @type {object}
|
|
@@ -1178,6 +1179,7 @@ router.post(
|
|
|
1178
1179
|
getState().getConfig("development_mode", false)
|
|
1179
1180
|
) {
|
|
1180
1181
|
await plugin.delete();
|
|
1182
|
+
await new PluginInstaller(plugin).remove();
|
|
1181
1183
|
req.flash("success", req.__(`Module %s removed.`, plugin.name));
|
|
1182
1184
|
} else {
|
|
1183
1185
|
req.flash(
|
|
@@ -1241,7 +1243,12 @@ router.post(
|
|
|
1241
1243
|
res.redirect(`/plugins`);
|
|
1242
1244
|
return;
|
|
1243
1245
|
}
|
|
1244
|
-
await load_plugins.loadAndSaveNewPlugin(
|
|
1246
|
+
const msgs = await load_plugins.loadAndSaveNewPlugin(
|
|
1247
|
+
plugin,
|
|
1248
|
+
forceReInstall,
|
|
1249
|
+
undefined,
|
|
1250
|
+
req.__
|
|
1251
|
+
);
|
|
1245
1252
|
const plugin_module = getState().plugins[name];
|
|
1246
1253
|
await sleep(1000); // Allow other workers to load this plugin
|
|
1247
1254
|
await getState().refresh_views();
|
|
@@ -1255,9 +1262,11 @@ router.post(
|
|
|
1255
1262
|
plugin_db.name
|
|
1256
1263
|
)
|
|
1257
1264
|
);
|
|
1265
|
+
if (msgs?.length > 0) req.flash("warning", msgs.join("<br>"));
|
|
1258
1266
|
res.redirect(`/plugins/configure/${plugin_db.name}`);
|
|
1259
1267
|
} else {
|
|
1260
1268
|
req.flash("success", req.__(`Module %s installed`, plugin.name));
|
|
1269
|
+
if (msgs?.length > 0) req.flash("warning", msgs.join("<br>"));
|
|
1261
1270
|
res.redirect(`/plugins`);
|
|
1262
1271
|
}
|
|
1263
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"
|
package/serve.js
CHANGED
|
@@ -105,7 +105,7 @@ const initMaster = async ({ disableMigrate }, useClusterAdaptor = true) => {
|
|
|
105
105
|
// migrate database
|
|
106
106
|
if (!disableMigrate) await migrate(db.connectObj.default_schema, true);
|
|
107
107
|
// load all plugins
|
|
108
|
-
await loadAllPlugins();
|
|
108
|
+
await loadAllPlugins(true);
|
|
109
109
|
// switch on sql logging - but it was initiated before???
|
|
110
110
|
if (getState().getConfig("log_sql", false)) db.set_sql_logging();
|
|
111
111
|
if (db.is_it_multi_tenant()) {
|
package/tests/page.test.js
CHANGED
|
@@ -30,7 +30,17 @@ const prepHtmlFiles = async () => {
|
|
|
30
30
|
const html = `<html><head><title>Landing page</title></head><body><h1>${content}</h1></body></html>`;
|
|
31
31
|
if (!existsSync(scFolder)) await File.new_folder(folder);
|
|
32
32
|
if (!existsSync(join(scFolder, name))) {
|
|
33
|
-
|
|
33
|
+
const file = await File.from_contents(
|
|
34
|
+
name,
|
|
35
|
+
"text/html",
|
|
36
|
+
html,
|
|
37
|
+
1,
|
|
38
|
+
1,
|
|
39
|
+
folder
|
|
40
|
+
);
|
|
41
|
+
file.location = File.absPathToServePath(file.location);
|
|
42
|
+
|
|
43
|
+
return file;
|
|
34
44
|
} else {
|
|
35
45
|
const file = await File.from_file_on_disk(name, scFolder);
|
|
36
46
|
fs.writeFileSync(file.location, html);
|