@saltcorn/server 0.9.5-beta.19 → 0.9.5-beta.20
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 +13 -2
- package/locales/ru.json +1134 -1101
- package/package.json +9 -9
- package/routes/actions.js +16 -1
- package/routes/admin.js +86 -3
- package/routes/api.js +4 -1
- package/routes/plugins.js +36 -0
- package/routes/search.js +28 -2
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.20",
|
|
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.20",
|
|
11
|
+
"@saltcorn/builder": "0.9.5-beta.20",
|
|
12
|
+
"@saltcorn/data": "0.9.5-beta.20",
|
|
13
|
+
"@saltcorn/admin-models": "0.9.5-beta.20",
|
|
14
|
+
"@saltcorn/filemanager": "0.9.5-beta.20",
|
|
15
|
+
"@saltcorn/markup": "0.9.5-beta.20",
|
|
16
|
+
"@saltcorn/plugins-loader": "0.9.5-beta.20",
|
|
17
|
+
"@saltcorn/sbadmin2": "0.9.5-beta.20",
|
|
18
18
|
"@socket.io/cluster-adapter": "^0.2.1",
|
|
19
19
|
"@socket.io/sticky": "^1.0.1",
|
|
20
20
|
"adm-zip": "0.5.10",
|
package/routes/actions.js
CHANGED
|
@@ -307,6 +307,14 @@ const triggerForm = async (req, trigger) => {
|
|
|
307
307
|
showIf: { when_trigger: ["API call"] },
|
|
308
308
|
options: roleOptions,
|
|
309
309
|
},
|
|
310
|
+
{
|
|
311
|
+
name: "_raw_output",
|
|
312
|
+
label: "Raw Output",
|
|
313
|
+
parent_field: "configuration",
|
|
314
|
+
sublabel: req.__("Do not wrap response in a success object"),
|
|
315
|
+
type: "Bool",
|
|
316
|
+
showIf: { when_trigger: ["API call"] },
|
|
317
|
+
},
|
|
310
318
|
],
|
|
311
319
|
});
|
|
312
320
|
// if (trigger) {
|
|
@@ -450,6 +458,11 @@ router.post(
|
|
|
450
458
|
},
|
|
451
459
|
});
|
|
452
460
|
} else {
|
|
461
|
+
if (form.values.configuration)
|
|
462
|
+
form.values.configuration = {
|
|
463
|
+
...trigger.configuration,
|
|
464
|
+
...form.values.configuration,
|
|
465
|
+
};
|
|
453
466
|
await Trigger.update(trigger.id, form.values); //{configuration: form.values});
|
|
454
467
|
req.flash("success", req.__("Action information saved"));
|
|
455
468
|
res.redirect(`/actions/`);
|
|
@@ -730,7 +743,9 @@ router.post(
|
|
|
730
743
|
},
|
|
731
744
|
});
|
|
732
745
|
} else {
|
|
733
|
-
await Trigger.update(trigger.id, {
|
|
746
|
+
await Trigger.update(trigger.id, {
|
|
747
|
+
configuration: { ...trigger.configuration, ...form.values },
|
|
748
|
+
});
|
|
734
749
|
if (req.xhr) {
|
|
735
750
|
res.json({ success: "ok" });
|
|
736
751
|
return;
|
package/routes/admin.js
CHANGED
|
@@ -284,9 +284,23 @@ router.get(
|
|
|
284
284
|
backupForm.values.auto_backup_destination = getState().getConfig(
|
|
285
285
|
"auto_backup_destination"
|
|
286
286
|
);
|
|
287
|
+
backupForm.values.auto_backup_tenants = getState().getConfig(
|
|
288
|
+
"auto_backup_tenants"
|
|
289
|
+
);
|
|
287
290
|
backupForm.values.auto_backup_directory = getState().getConfig(
|
|
288
291
|
"auto_backup_directory"
|
|
289
292
|
);
|
|
293
|
+
backupForm.values.auto_backup_username = getState().getConfig(
|
|
294
|
+
"auto_backup_username"
|
|
295
|
+
);
|
|
296
|
+
backupForm.values.auto_backup_server =
|
|
297
|
+
getState().getConfig("auto_backup_server");
|
|
298
|
+
backupForm.values.auto_backup_password = getState().getConfig(
|
|
299
|
+
"auto_backup_password"
|
|
300
|
+
);
|
|
301
|
+
backupForm.values.auto_backup_port =
|
|
302
|
+
getState().getConfig("auto_backup_port");
|
|
303
|
+
|
|
290
304
|
backupForm.values.auto_backup_expire_days = getState().getConfig(
|
|
291
305
|
"auto_backup_expire_days"
|
|
292
306
|
);
|
|
@@ -691,8 +705,10 @@ const backupFilePrefixForm = (req) =>
|
|
|
691
705
|
* @param {object} req
|
|
692
706
|
* @returns {Form} form
|
|
693
707
|
*/
|
|
694
|
-
const autoBackupForm = (req) =>
|
|
695
|
-
|
|
708
|
+
const autoBackupForm = (req) => {
|
|
709
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
710
|
+
|
|
711
|
+
return new Form({
|
|
696
712
|
action: "/admin/set-auto-backup",
|
|
697
713
|
onChange: `saveAndContinue(this);$('#btnBackupNow').prop('disabled', $('#inputauto_backup_frequency').val()==='Never');`,
|
|
698
714
|
noSubmitButton: true,
|
|
@@ -718,7 +734,47 @@ const autoBackupForm = (req) =>
|
|
|
718
734
|
name: "auto_backup_destination",
|
|
719
735
|
required: true,
|
|
720
736
|
showIf: { auto_backup_frequency: ["Daily", "Weekly"] },
|
|
721
|
-
attributes: {
|
|
737
|
+
attributes: {
|
|
738
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
739
|
+
options: ["Saltcorn files", "Local directory", "SFTP server"],
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
type: "String",
|
|
744
|
+
label: req.__("Server host"),
|
|
745
|
+
name: "auto_backup_server",
|
|
746
|
+
showIf: {
|
|
747
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
748
|
+
auto_backup_destination: "SFTP server",
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
type: "String",
|
|
753
|
+
label: req.__("Username"),
|
|
754
|
+
name: "auto_backup_username",
|
|
755
|
+
showIf: {
|
|
756
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
757
|
+
auto_backup_destination: "SFTP server",
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
type: "String",
|
|
762
|
+
label: req.__("Password"),
|
|
763
|
+
fieldview: "password",
|
|
764
|
+
name: "auto_backup_password",
|
|
765
|
+
showIf: {
|
|
766
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
767
|
+
auto_backup_destination: "SFTP server",
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
type: "Integer",
|
|
772
|
+
label: req.__("Port"),
|
|
773
|
+
name: "auto_backup_port",
|
|
774
|
+
showIf: {
|
|
775
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
776
|
+
auto_backup_destination: "SFTP server",
|
|
777
|
+
},
|
|
722
778
|
},
|
|
723
779
|
{
|
|
724
780
|
type: "String",
|
|
@@ -730,6 +786,19 @@ const autoBackupForm = (req) =>
|
|
|
730
786
|
//auto_backup_destination: "Local directory",
|
|
731
787
|
},
|
|
732
788
|
},
|
|
789
|
+
{
|
|
790
|
+
type: "String",
|
|
791
|
+
label: req.__("Retain local directory"),
|
|
792
|
+
name: "auto_backup_retain_local_directory",
|
|
793
|
+
sublabel: req.__(
|
|
794
|
+
"Retain a local backup copy in this directory (optional)"
|
|
795
|
+
),
|
|
796
|
+
showIf: {
|
|
797
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
798
|
+
auto_backup_destination: "SFTP server",
|
|
799
|
+
//auto_backup_destination: "Local directory",
|
|
800
|
+
},
|
|
801
|
+
},
|
|
733
802
|
{
|
|
734
803
|
type: "Integer",
|
|
735
804
|
label: req.__("Expiration in days"),
|
|
@@ -742,6 +811,19 @@ const autoBackupForm = (req) =>
|
|
|
742
811
|
auto_backup_destination: "Local directory",
|
|
743
812
|
},
|
|
744
813
|
},
|
|
814
|
+
...(isRoot
|
|
815
|
+
? [
|
|
816
|
+
{
|
|
817
|
+
type: "Bool",
|
|
818
|
+
label: req.__("All tenants"),
|
|
819
|
+
sublabel: req.__("Also backup all tenants"),
|
|
820
|
+
name: "auto_backup_tenants",
|
|
821
|
+
showIf: {
|
|
822
|
+
auto_backup_frequency: ["Daily", "Weekly"],
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
]
|
|
826
|
+
: []),
|
|
745
827
|
{
|
|
746
828
|
type: "Bool",
|
|
747
829
|
label: req.__("Include Event Logs"),
|
|
@@ -753,6 +835,7 @@ const autoBackupForm = (req) =>
|
|
|
753
835
|
},
|
|
754
836
|
],
|
|
755
837
|
});
|
|
838
|
+
};
|
|
756
839
|
|
|
757
840
|
/**
|
|
758
841
|
* Snapshot Form
|
package/routes/api.js
CHANGED
|
@@ -373,7 +373,10 @@ router.all(
|
|
|
373
373
|
res.redirect(resp.goto);
|
|
374
374
|
else if (req.headers?.scgotourl)
|
|
375
375
|
res.redirect(req.headers?.scgotourl);
|
|
376
|
-
else
|
|
376
|
+
else {
|
|
377
|
+
if (trigger.configuration?._raw_output) res.json({ resp });
|
|
378
|
+
else res.json({ success: true, data: resp });
|
|
379
|
+
}
|
|
377
380
|
} catch (e) {
|
|
378
381
|
Crash.create(e, req);
|
|
379
382
|
res.status(400).json({ success: false, error: e.message });
|
package/routes/plugins.js
CHANGED
|
@@ -547,6 +547,16 @@ const plugin_store_html = (items, req) => {
|
|
|
547
547
|
};
|
|
548
548
|
};
|
|
549
549
|
|
|
550
|
+
const flash_relogin = (req, exposedConfigs) => {
|
|
551
|
+
req.flash(
|
|
552
|
+
"warning",
|
|
553
|
+
req.__(
|
|
554
|
+
"To see changes for '%s' in show-if-formulas, users need to relogin",
|
|
555
|
+
exposedConfigs.join(", ")
|
|
556
|
+
)
|
|
557
|
+
);
|
|
558
|
+
};
|
|
559
|
+
|
|
550
560
|
/**
|
|
551
561
|
* @name get
|
|
552
562
|
* @function
|
|
@@ -789,6 +799,8 @@ router.post(
|
|
|
789
799
|
const instore = await Plugin.store_plugins_available();
|
|
790
800
|
const store_plugin = instore.find((p) => p.name === plugin.name);
|
|
791
801
|
if (store_plugin && store_plugin.has_auth) flash_restart(req);
|
|
802
|
+
if (module.exposed_configs?.length > 0)
|
|
803
|
+
flash_relogin(req, module.exposed_configs);
|
|
792
804
|
getState().processSend({
|
|
793
805
|
refresh_plugin_cfg: plugin.name,
|
|
794
806
|
tenant: db.getTenantSchema(),
|
|
@@ -936,6 +948,14 @@ router.post(
|
|
|
936
948
|
...(plugin.configuration ? plugin.configuration : {}),
|
|
937
949
|
...values,
|
|
938
950
|
});
|
|
951
|
+
const sessionUser = req.session?.passport?.user;
|
|
952
|
+
if (sessionUser) {
|
|
953
|
+
const pluginName = module.plugin_name;
|
|
954
|
+
if (sessionUser.attributes) {
|
|
955
|
+
const oldAttrs = sessionUser.attributes[pluginName] || {};
|
|
956
|
+
sessionUser.attributes[pluginName] = { ...oldAttrs, ...values };
|
|
957
|
+
} else sessionUser.attributes = { [pluginName]: values };
|
|
958
|
+
}
|
|
939
959
|
getState().processSend({
|
|
940
960
|
refresh_plugin_cfg: plugin.name,
|
|
941
961
|
tenant: db.getTenantSchema(),
|
|
@@ -976,6 +996,14 @@ router.post(
|
|
|
976
996
|
getState().userLayouts[req.user.email] = module.layout(
|
|
977
997
|
userAttrs.layout.config
|
|
978
998
|
);
|
|
999
|
+
const sessionUser = req.session?.passport?.user;
|
|
1000
|
+
if (sessionUser) {
|
|
1001
|
+
const pluginName = module.plugin_name;
|
|
1002
|
+
if (sessionUser.attributes) {
|
|
1003
|
+
const oldAttrs = sessionUser.attributes[pluginName] || {};
|
|
1004
|
+
sessionUser.attributes[pluginName] = { ...oldAttrs, ...values };
|
|
1005
|
+
} else sessionUser.attributes = { [pluginName]: values };
|
|
1006
|
+
}
|
|
979
1007
|
getState().processSend({
|
|
980
1008
|
refresh_plugin_cfg: plugin.name,
|
|
981
1009
|
tenant: db.getTenantSchema(),
|
|
@@ -997,6 +1025,14 @@ router.post(
|
|
|
997
1025
|
delete userAttrs.layout;
|
|
998
1026
|
await user.update({ _attributes: userAttrs });
|
|
999
1027
|
getState().userLayouts[req.user.email] = null;
|
|
1028
|
+
let module = getState().plugins[plugin];
|
|
1029
|
+
if (!module) {
|
|
1030
|
+
module = getState().plugins[getState().plugin_module_names[plugin]];
|
|
1031
|
+
}
|
|
1032
|
+
const pluginName = module.plugin_name;
|
|
1033
|
+
const sessionUser = req.session?.passport?.user;
|
|
1034
|
+
if (sessionUser?.attributes[pluginName])
|
|
1035
|
+
sessionUser.attributes[pluginName] = {};
|
|
1000
1036
|
getState().processSend({
|
|
1001
1037
|
refresh_plugin_cfg: plugin,
|
|
1002
1038
|
tenant: db.getTenantSchema(),
|
package/routes/search.js
CHANGED
|
@@ -52,6 +52,12 @@ const searchConfigForm = (tables, views, req) => {
|
|
|
52
52
|
attributes: { options: ok_views.map((v) => v.name).join() },
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
+
fields.push({
|
|
56
|
+
name: "search_table_description",
|
|
57
|
+
label: req.__("Description header"),
|
|
58
|
+
sublabel: req.__("Use table description instead of name as header"),
|
|
59
|
+
type: "Bool",
|
|
60
|
+
});
|
|
55
61
|
const blurb1 = req.__(
|
|
56
62
|
`Choose views for <a href="/search">search results</a> for each table.<br/>Set to blank to omit table from global search.`
|
|
57
63
|
);
|
|
@@ -84,7 +90,11 @@ router.get(
|
|
|
84
90
|
const views = await View.find({}, { orderBy: "name" });
|
|
85
91
|
const tables = await Table.find();
|
|
86
92
|
const form = searchConfigForm(tables, views, req);
|
|
87
|
-
form.values = getState().getConfig("globalSearch");
|
|
93
|
+
form.values = getState().getConfig("globalSearch", {});
|
|
94
|
+
form.values.search_table_description = getState().getConfig(
|
|
95
|
+
"search_table_description",
|
|
96
|
+
false
|
|
97
|
+
);
|
|
88
98
|
send_infoarch_page({
|
|
89
99
|
res,
|
|
90
100
|
req,
|
|
@@ -116,6 +126,13 @@ router.post(
|
|
|
116
126
|
const result = form.validate(req.body);
|
|
117
127
|
|
|
118
128
|
if (result.success) {
|
|
129
|
+
const search_table_description =
|
|
130
|
+
!!result.success.search_table_description;
|
|
131
|
+
await getState().setConfig(
|
|
132
|
+
"search_table_description",
|
|
133
|
+
search_table_description
|
|
134
|
+
);
|
|
135
|
+
delete result.success.search_table_description;
|
|
119
136
|
await getState().setConfig("globalSearch", result.success);
|
|
120
137
|
if (!req.xhr) res.redirect("/search/config");
|
|
121
138
|
else res.json({ success: "ok" });
|
|
@@ -175,6 +192,10 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
175
192
|
res.redirect("/");
|
|
176
193
|
return;
|
|
177
194
|
}
|
|
195
|
+
const search_table_description = getState().getConfig(
|
|
196
|
+
"search_table_description",
|
|
197
|
+
false
|
|
198
|
+
);
|
|
178
199
|
const current_page = parseInt(_page) || 1;
|
|
179
200
|
const offset = (current_page - 1) * page_size;
|
|
180
201
|
let resp = [];
|
|
@@ -184,6 +205,11 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
184
205
|
if (!viewName || viewName === "") continue;
|
|
185
206
|
tablesConfigured += 1;
|
|
186
207
|
if (table && tableName !== table) continue;
|
|
208
|
+
let sectionHeader = tableName;
|
|
209
|
+
if (search_table_description) {
|
|
210
|
+
sectionHeader =
|
|
211
|
+
Table.findOne({ name: tableName })?.description || tableName;
|
|
212
|
+
}
|
|
187
213
|
const view = await View.findOne({ name: viewName });
|
|
188
214
|
if (!view)
|
|
189
215
|
throw new InvalidConfiguration(
|
|
@@ -209,7 +235,7 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
209
235
|
tablesWithResults.push(tableName);
|
|
210
236
|
resp.push({
|
|
211
237
|
type: "card",
|
|
212
|
-
title: span({ id: tableName },
|
|
238
|
+
title: span({ id: tableName }, sectionHeader),
|
|
213
239
|
contents: vresps.map((vr) => vr.html).join("<hr>") + paginate,
|
|
214
240
|
});
|
|
215
241
|
}
|