@saltcorn/server 0.9.5-beta.8 → 0.9.5
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/routes.js +54 -7
- package/help/Android App Signing.tmd +22 -0
- package/help/Provisioning Profile.tmd +60 -0
- package/help/xcodebuild.tmd +1 -0
- package/load_plugins.js +6 -0
- package/locales/en.json +27 -2
- package/locales/ru.json +1134 -1101
- package/package.json +9 -9
- package/public/saltcorn-common.js +174 -21
- package/public/saltcorn.css +4 -0
- package/public/saltcorn.js +85 -63
- package/restart_watcher.js +2 -0
- package/routes/actions.js +17 -1
- package/routes/admin.js +657 -77
- package/routes/api.js +4 -1
- package/routes/fields.js +107 -9
- package/routes/menu.js +12 -3
- package/routes/page.js +8 -2
- package/routes/pageedit.js +1 -1
- package/routes/plugins.js +266 -30
- package/routes/search.js +28 -2
- package/routes/tag_entries.js +3 -3
- package/routes/tenant.js +1 -0
- package/routes/utils.js +18 -2
- package/routes/view.js +15 -3
- package/s3storage.js +1 -0
- package/systemd.js +1 -1
- package/tests/page.test.js +11 -1
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/fields.js
CHANGED
|
@@ -251,6 +251,16 @@ const fieldFlow = (req) =>
|
|
|
251
251
|
: ""
|
|
252
252
|
}row).${model_output}`;
|
|
253
253
|
}
|
|
254
|
+
if (context.expression_type === "Aggregation") {
|
|
255
|
+
expression = "__aggregation";
|
|
256
|
+
attributes.agg_relation = context.agg_relation;
|
|
257
|
+
attributes.agg_field = context.agg_field;
|
|
258
|
+
attributes.aggwhere = context.aggwhere;
|
|
259
|
+
attributes.aggregate = context.aggregate;
|
|
260
|
+
const [table, ref] = context.agg_relation.split(".");
|
|
261
|
+
attributes.table = table;
|
|
262
|
+
attributes.ref = ref;
|
|
263
|
+
}
|
|
254
264
|
const { reftable_name, type } = calcFieldType(context.type);
|
|
255
265
|
const fldRow = {
|
|
256
266
|
table_id,
|
|
@@ -421,6 +431,50 @@ const fieldFlow = (req) =>
|
|
|
421
431
|
);
|
|
422
432
|
output_options[model.name] = outputs.map((o) => o.name);
|
|
423
433
|
}
|
|
434
|
+
const aggStatOptions = {};
|
|
435
|
+
|
|
436
|
+
const { child_field_list, child_relations } =
|
|
437
|
+
await table.get_child_relations(true);
|
|
438
|
+
const agg_field_opts = child_relations.map(
|
|
439
|
+
({ table, key_field, through }) => {
|
|
440
|
+
const aggKey =
|
|
441
|
+
(through ? `${through.name}->` : "") +
|
|
442
|
+
`${table.name}.${key_field.name}`;
|
|
443
|
+
aggStatOptions[aggKey] = [
|
|
444
|
+
"Count",
|
|
445
|
+
"CountUnique",
|
|
446
|
+
"Avg",
|
|
447
|
+
"Sum",
|
|
448
|
+
"Max",
|
|
449
|
+
"Min",
|
|
450
|
+
"Array_Agg",
|
|
451
|
+
];
|
|
452
|
+
table.fields.forEach((f) => {
|
|
453
|
+
if (f.type && f.type.name === "Date") {
|
|
454
|
+
aggStatOptions[aggKey].push(`Latest ${f.name}`);
|
|
455
|
+
aggStatOptions[aggKey].push(`Earliest ${f.name}`);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
return {
|
|
459
|
+
name: `agg_field`,
|
|
460
|
+
label: req.__("On Field"),
|
|
461
|
+
type: "String",
|
|
462
|
+
required: true,
|
|
463
|
+
attributes: {
|
|
464
|
+
options: table.fields
|
|
465
|
+
.filter((f) => !f.calculated || f.stored)
|
|
466
|
+
.map((f) => ({
|
|
467
|
+
label: f.name,
|
|
468
|
+
name: `${f.name}@${f.type_name}`,
|
|
469
|
+
})),
|
|
470
|
+
},
|
|
471
|
+
showIf: {
|
|
472
|
+
agg_relation: aggKey,
|
|
473
|
+
expression_type: "Aggregation",
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
);
|
|
424
478
|
return new Form({
|
|
425
479
|
fields: [
|
|
426
480
|
{
|
|
@@ -429,9 +483,43 @@ const fieldFlow = (req) =>
|
|
|
429
483
|
input_type: "select",
|
|
430
484
|
options: [
|
|
431
485
|
"JavaScript expression",
|
|
486
|
+
...(child_relations.length && context.stored
|
|
487
|
+
? ["Aggregation"]
|
|
488
|
+
: []),
|
|
432
489
|
...(models.length ? ["Model prediction"] : []),
|
|
433
490
|
],
|
|
434
491
|
},
|
|
492
|
+
{
|
|
493
|
+
name: "agg_relation",
|
|
494
|
+
label: req.__("Relation"),
|
|
495
|
+
type: "String",
|
|
496
|
+
required: true,
|
|
497
|
+
attributes: {
|
|
498
|
+
options: child_field_list,
|
|
499
|
+
},
|
|
500
|
+
showIf: { expression_type: "Aggregation" },
|
|
501
|
+
},
|
|
502
|
+
...agg_field_opts,
|
|
503
|
+
{
|
|
504
|
+
name: "aggregate",
|
|
505
|
+
label: req.__("Statistic"),
|
|
506
|
+
type: "String",
|
|
507
|
+
required: true,
|
|
508
|
+
attributes: {
|
|
509
|
+
calcOptions: ["agg_relation", aggStatOptions],
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
showIf: { expression_type: "Aggregation" },
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
name: "aggwhere",
|
|
516
|
+
label: req.__("Where"),
|
|
517
|
+
sublabel: req.__("Formula"),
|
|
518
|
+
class: "validate-expression",
|
|
519
|
+
type: "String",
|
|
520
|
+
required: false,
|
|
521
|
+
showIf: { expression_type: "Aggregation" },
|
|
522
|
+
},
|
|
435
523
|
{
|
|
436
524
|
name: "model",
|
|
437
525
|
label: req.__("Model"),
|
|
@@ -487,7 +575,9 @@ const fieldFlow = (req) =>
|
|
|
487
575
|
new Field({
|
|
488
576
|
name: "test_btn",
|
|
489
577
|
label: req.__("Test"),
|
|
490
|
-
showIf: {
|
|
578
|
+
showIf: {
|
|
579
|
+
expression_type: ["JavaScript expression"],
|
|
580
|
+
},
|
|
491
581
|
// todo sublabel
|
|
492
582
|
input_type: "custom_html",
|
|
493
583
|
attributes: {
|
|
@@ -652,6 +742,9 @@ router.get(
|
|
|
652
742
|
{
|
|
653
743
|
...field.toJson,
|
|
654
744
|
...field.attributes,
|
|
745
|
+
...(field.expression === "__aggregation"
|
|
746
|
+
? { expression_type: "Aggregation" }
|
|
747
|
+
: {}),
|
|
655
748
|
},
|
|
656
749
|
req
|
|
657
750
|
);
|
|
@@ -955,8 +1048,11 @@ router.post(
|
|
|
955
1048
|
}
|
|
956
1049
|
return;
|
|
957
1050
|
}
|
|
1051
|
+
}
|
|
1052
|
+
if (targetField.type === "File") {
|
|
1053
|
+
fv = getState().fileviews[fieldview];
|
|
958
1054
|
} else {
|
|
959
|
-
fv = targetField.type
|
|
1055
|
+
fv = targetField.type?.fieldviews?.[fieldview];
|
|
960
1056
|
if (!fv)
|
|
961
1057
|
fv =
|
|
962
1058
|
targetField.type.fieldviews.show ||
|
|
@@ -994,13 +1090,15 @@ router.post(
|
|
|
994
1090
|
if (oldRow) {
|
|
995
1091
|
const value = oldRow[kpath[kpath.length - 1]];
|
|
996
1092
|
//TODO run fieldview
|
|
997
|
-
res.send(
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1093
|
+
if (value === null || typeof value === "undefined") res.send("");
|
|
1094
|
+
else
|
|
1095
|
+
res.send(
|
|
1096
|
+
typeof value === "string"
|
|
1097
|
+
? value
|
|
1098
|
+
: value?.toString
|
|
1099
|
+
? value.toString()
|
|
1100
|
+
: `${value}`
|
|
1101
|
+
);
|
|
1004
1102
|
return;
|
|
1005
1103
|
}
|
|
1006
1104
|
}
|
package/routes/menu.js
CHANGED
|
@@ -19,7 +19,7 @@ const { save_menu_items } = require("@saltcorn/data/models/config");
|
|
|
19
19
|
const db = require("@saltcorn/data/db");
|
|
20
20
|
|
|
21
21
|
const { renderForm } = require("@saltcorn/markup");
|
|
22
|
-
const { script, domReady, div, ul } = require("@saltcorn/markup/tags");
|
|
22
|
+
const { script, domReady, div, ul, i } = require("@saltcorn/markup/tags");
|
|
23
23
|
const { send_infoarch_page } = require("../markup/admin.js");
|
|
24
24
|
const Table = require("@saltcorn/data/models/table");
|
|
25
25
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
@@ -368,7 +368,7 @@ const menuEditorScript = (menu_items) => `
|
|
|
368
368
|
iconPicker: iconPickerOptions,
|
|
369
369
|
getLabelText: (item) => item?.text || item?.type,
|
|
370
370
|
labelEdit: 'Edit <i class="fas fa-edit clickable"></i>',
|
|
371
|
-
maxLevel:
|
|
371
|
+
maxLevel: 2 // (Optional) Default is -1 (no level limit)
|
|
372
372
|
// Valid levels are from [0, 1, 2, 3,...N]
|
|
373
373
|
});
|
|
374
374
|
editor.setForm($('#menuForm'));
|
|
@@ -446,7 +446,16 @@ router.get(
|
|
|
446
446
|
above: [
|
|
447
447
|
{
|
|
448
448
|
besides: [
|
|
449
|
-
div(
|
|
449
|
+
div(
|
|
450
|
+
ul({ id: "myEditor", class: "sortableLists list-group" }),
|
|
451
|
+
div(
|
|
452
|
+
i(
|
|
453
|
+
req.__(
|
|
454
|
+
"Some themes support only one level of menu nesting."
|
|
455
|
+
)
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
),
|
|
450
459
|
div(
|
|
451
460
|
renderForm(form, req.csrfToken()),
|
|
452
461
|
script(domReady(menuEditorScript(menu_items)))
|
package/routes/page.js
CHANGED
|
@@ -135,7 +135,13 @@ router.get(
|
|
|
135
135
|
"/:pagename",
|
|
136
136
|
error_catcher(async (req, res) => {
|
|
137
137
|
const { pagename } = req.params;
|
|
138
|
-
|
|
138
|
+
const state = getState();
|
|
139
|
+
state.log(
|
|
140
|
+
3,
|
|
141
|
+
`Route /page/${pagename} user=${req.user?.id}${
|
|
142
|
+
state.getConfig("log_ip_address", false) ? ` IP=${req.ip}` : ""
|
|
143
|
+
}`
|
|
144
|
+
);
|
|
139
145
|
const tic = new Date();
|
|
140
146
|
const { page, pageGroup } = findPageOrGroup(pagename);
|
|
141
147
|
if (page) await runPage(page, req, res, tic);
|
|
@@ -144,7 +150,7 @@ router.get(
|
|
|
144
150
|
if ((page || pageGroup) && !req.user) {
|
|
145
151
|
res.redirect(`/auth/login?dest=${encodeURIComponent(req.originalUrl)}`);
|
|
146
152
|
} else {
|
|
147
|
-
|
|
153
|
+
state.log(2, `Page ${pagename} not found or not authorized`);
|
|
148
154
|
res
|
|
149
155
|
.status(404)
|
|
150
156
|
.sendWrap(
|
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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
|
-
const { isAdmin, error_catcher } = require("./utils.js");
|
|
9
|
+
const { isAdmin, loggedIn, error_catcher } = require("./utils.js");
|
|
10
10
|
const { renderForm, link, post_btn } = require("@saltcorn/markup");
|
|
11
11
|
const {
|
|
12
12
|
getState,
|
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
const Form = require("@saltcorn/data/models/form");
|
|
17
17
|
const Field = require("@saltcorn/data/models/field");
|
|
18
18
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
19
|
+
const User = require("@saltcorn/data/models/user");
|
|
19
20
|
const { fetch_available_packs } = require("@saltcorn/admin-models/models/pack");
|
|
20
21
|
const {
|
|
21
22
|
upgrade_all_tenants_plugins,
|
|
@@ -362,7 +363,9 @@ const storeNavPills = (req) => {
|
|
|
362
363
|
{ class: "nav-item" },
|
|
363
364
|
a(
|
|
364
365
|
{
|
|
365
|
-
href: `/plugins?set=${txt.toLowerCase()}
|
|
366
|
+
href: `/plugins?set=${txt.toLowerCase()}${
|
|
367
|
+
req.query.q ? `&q=${req.query.q}` : ""
|
|
368
|
+
}`,
|
|
366
369
|
class: [
|
|
367
370
|
"nav-link",
|
|
368
371
|
(req.query.set === txt.toLowerCase() ||
|
|
@@ -546,6 +549,16 @@ const plugin_store_html = (items, req) => {
|
|
|
546
549
|
};
|
|
547
550
|
};
|
|
548
551
|
|
|
552
|
+
const flash_relogin = (req, exposedConfigs) => {
|
|
553
|
+
req.flash(
|
|
554
|
+
"warning",
|
|
555
|
+
req.__(
|
|
556
|
+
"To see changes for '%s' in show-if-formulas, users need to relogin",
|
|
557
|
+
exposedConfigs.join(", ")
|
|
558
|
+
)
|
|
559
|
+
);
|
|
560
|
+
};
|
|
561
|
+
|
|
549
562
|
/**
|
|
550
563
|
* @name get
|
|
551
564
|
* @function
|
|
@@ -697,7 +710,12 @@ router.get(
|
|
|
697
710
|
label: "Reload page to see changes",
|
|
698
711
|
id: "btnReloadNow",
|
|
699
712
|
class: "btn btn-outline-secondary",
|
|
700
|
-
onclick:
|
|
713
|
+
onclick: `if (window.savingViewConfig)
|
|
714
|
+
notifyAlert({
|
|
715
|
+
type: 'danger',
|
|
716
|
+
text: 'Still saving, please wait',
|
|
717
|
+
});
|
|
718
|
+
else location.reload();`,
|
|
701
719
|
},
|
|
702
720
|
];
|
|
703
721
|
wfres.renderForm.onChange = `${
|
|
@@ -705,25 +723,31 @@ router.get(
|
|
|
705
723
|
};$('#btnReloadNow').removeClass('btn-outline-secondary').addClass('btn-secondary')`;
|
|
706
724
|
}
|
|
707
725
|
|
|
708
|
-
res.sendWrap(
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
726
|
+
res.sendWrap(
|
|
727
|
+
{
|
|
728
|
+
title: req.__(`Configure %s Plugin`, plugin.name),
|
|
729
|
+
headers: wfres.renderForm?.additionalHeaders || [],
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
above: [
|
|
733
|
+
{
|
|
734
|
+
type: "breadcrumbs",
|
|
735
|
+
crumbs: [
|
|
736
|
+
{ text: req.__("Settings"), href: "/settings" },
|
|
737
|
+
{ text: req.__("Module store"), href: "/plugins" },
|
|
738
|
+
{ text: plugin.name },
|
|
739
|
+
],
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
type: "card",
|
|
743
|
+
class: "mt-0",
|
|
744
|
+
title: req.__(`Configure %s Plugin`, plugin.name),
|
|
745
|
+
titleAjaxIndicator: true,
|
|
746
|
+
contents: renderForm(wfres.renderForm, req.csrfToken()),
|
|
747
|
+
},
|
|
748
|
+
],
|
|
749
|
+
}
|
|
750
|
+
);
|
|
727
751
|
})
|
|
728
752
|
);
|
|
729
753
|
|
|
@@ -770,17 +794,21 @@ router.post(
|
|
|
770
794
|
contents: renderForm(wfres.renderForm, req.csrfToken()),
|
|
771
795
|
});
|
|
772
796
|
} else {
|
|
773
|
-
|
|
797
|
+
const newCfg = wfres.cleanup ? wfres.context : wfres;
|
|
798
|
+
plugin.configuration = newCfg;
|
|
774
799
|
await plugin.upsert();
|
|
775
800
|
await load_plugins.loadPlugin(plugin);
|
|
776
801
|
const instore = await Plugin.store_plugins_available();
|
|
777
802
|
const store_plugin = instore.find((p) => p.name === plugin.name);
|
|
778
803
|
if (store_plugin && store_plugin.has_auth) flash_restart(req);
|
|
804
|
+
if (module.exposed_configs?.length > 0)
|
|
805
|
+
flash_relogin(req, module.exposed_configs);
|
|
779
806
|
getState().processSend({
|
|
780
807
|
refresh_plugin_cfg: plugin.name,
|
|
781
808
|
tenant: db.getTenantSchema(),
|
|
782
809
|
});
|
|
783
810
|
if (module.layout) await sleep(500); // Allow other workers to reload this plugin
|
|
811
|
+
if (wfres.cleanup) await wfres.cleanup();
|
|
784
812
|
res.redirect("/plugins");
|
|
785
813
|
}
|
|
786
814
|
})
|
|
@@ -799,22 +827,223 @@ router.post(
|
|
|
799
827
|
const flow = module.configuration_workflow();
|
|
800
828
|
const step = await flow.singleStepForm(req.body, req);
|
|
801
829
|
if (step?.renderForm) {
|
|
802
|
-
if (
|
|
830
|
+
if (step.renderForm.hasErrors || step.savingErrors)
|
|
831
|
+
res.status(400).send(step.savingErrors || "Error");
|
|
832
|
+
else {
|
|
803
833
|
plugin.configuration = {
|
|
804
834
|
...plugin.configuration,
|
|
805
835
|
...step.renderForm.values,
|
|
806
836
|
};
|
|
807
837
|
await plugin.upsert();
|
|
808
838
|
await load_plugins.loadPlugin(plugin);
|
|
809
|
-
getState().processSend({
|
|
810
|
-
refresh_plugin_cfg: plugin.name,
|
|
811
|
-
tenant: db.getTenantSchema(),
|
|
812
|
-
});
|
|
813
|
-
res.json({ success: "ok" });
|
|
814
839
|
}
|
|
840
|
+
getState().processSend({
|
|
841
|
+
refresh_plugin_cfg: plugin.name,
|
|
842
|
+
tenant: db.getTenantSchema(),
|
|
843
|
+
});
|
|
844
|
+
res.json({ success: "ok" });
|
|
845
|
+
}
|
|
846
|
+
})
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
router.get(
|
|
850
|
+
"/user_configure/:name",
|
|
851
|
+
loggedIn,
|
|
852
|
+
error_catcher(async (req, res) => {
|
|
853
|
+
const user = await User.findOne({ id: req.user?.id });
|
|
854
|
+
if (!user) {
|
|
855
|
+
req.flash("error", req.__("Not authorized"));
|
|
856
|
+
return res.redirect("/");
|
|
857
|
+
}
|
|
858
|
+
const { name } = req.params;
|
|
859
|
+
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
860
|
+
if (!plugin) {
|
|
861
|
+
req.flash("warning", req.__("Module not found"));
|
|
862
|
+
return res.redirect("/auth/settings");
|
|
863
|
+
}
|
|
864
|
+
let module = getState().plugins[plugin.name];
|
|
865
|
+
if (!module) {
|
|
866
|
+
module = getState().plugins[getState().plugin_module_names[plugin.name]];
|
|
867
|
+
}
|
|
868
|
+
const form = await module.user_config_form({
|
|
869
|
+
...(plugin.configuration || {}),
|
|
870
|
+
...(user._attributes?.layout?.config || {}),
|
|
871
|
+
});
|
|
872
|
+
form.action = `/plugins/user_configure/${encodeURIComponent(plugin.name)}`;
|
|
873
|
+
form.onChange = `applyViewConfig(this, '/plugins/user_saveconfig/${encodeURIComponent(
|
|
874
|
+
name
|
|
875
|
+
)}', null, event);$('#btnReloadNow').removeClass('btn-outline-secondary').addClass('btn-secondary')`;
|
|
876
|
+
|
|
877
|
+
form.additionalButtons = [
|
|
878
|
+
{
|
|
879
|
+
label: "Reload page to see changes",
|
|
880
|
+
id: "btnReloadNow",
|
|
881
|
+
class: "btn btn-outline-secondary",
|
|
882
|
+
onclick: "location.reload();",
|
|
883
|
+
},
|
|
884
|
+
];
|
|
885
|
+
form.submitLabel = req.__("Finish") + " »";
|
|
886
|
+
res.sendWrap(
|
|
887
|
+
{
|
|
888
|
+
title: req.__(`Configure %s Plugin for %s`, plugin.name, user.email),
|
|
889
|
+
headers: form.additionalHeaders || [],
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
above: [
|
|
893
|
+
{
|
|
894
|
+
type: "breadcrumbs",
|
|
895
|
+
crumbs: [
|
|
896
|
+
{ text: req.__("Settings"), href: "/settings" },
|
|
897
|
+
{ text: req.__("Module store"), href: "/plugins" },
|
|
898
|
+
{ text: plugin.name },
|
|
899
|
+
],
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
type: "card",
|
|
903
|
+
class: "mt-0",
|
|
904
|
+
title: req.__(`Configure %s Plugin`, plugin.name),
|
|
905
|
+
titleAjaxIndicator: true,
|
|
906
|
+
contents: renderForm(form, req.csrfToken()),
|
|
907
|
+
},
|
|
908
|
+
],
|
|
909
|
+
}
|
|
910
|
+
);
|
|
911
|
+
})
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
router.post(
|
|
915
|
+
"/user_configure/:name",
|
|
916
|
+
loggedIn,
|
|
917
|
+
error_catcher(async (req, res) => {
|
|
918
|
+
const user = await User.findOne({ id: req.user?.id });
|
|
919
|
+
if (!user) {
|
|
920
|
+
req.flash("error", req.__("Not authorized"));
|
|
921
|
+
return res.redirect("/");
|
|
815
922
|
}
|
|
923
|
+
const { name } = req.params;
|
|
924
|
+
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
925
|
+
let module = getState().plugins[plugin.name];
|
|
926
|
+
if (!module) {
|
|
927
|
+
module = getState().plugins[getState().plugin_module_names[plugin.name]];
|
|
928
|
+
}
|
|
929
|
+
const form = await module.user_config_form({
|
|
930
|
+
...(plugin.configuration || {}),
|
|
931
|
+
...(user._attributes?.layout?.config || {}),
|
|
932
|
+
});
|
|
933
|
+
const valResult = form.validate(req.body);
|
|
934
|
+
if (form.hasErrors) {
|
|
935
|
+
req.flash("warning", req.__("An error occurred"));
|
|
936
|
+
return res.sendWrap(
|
|
937
|
+
req.__(`Configure %s Plugin for %s`, plugin.name, user.email),
|
|
938
|
+
renderForm(form, req.csrfToken())
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
const values = valResult.success;
|
|
942
|
+
values.is_user_config = true;
|
|
943
|
+
const userAttrs = user._attributes ? { ...user._attributes } : {};
|
|
944
|
+
userAttrs.layout = {
|
|
945
|
+
plugin: plugin.name,
|
|
946
|
+
config: values,
|
|
947
|
+
};
|
|
948
|
+
await user.update({ _attributes: userAttrs });
|
|
949
|
+
getState().userLayouts[req.user.email] = module.layout({
|
|
950
|
+
...(plugin.configuration ? plugin.configuration : {}),
|
|
951
|
+
...values,
|
|
952
|
+
});
|
|
953
|
+
const sessionUser = req.session?.passport?.user;
|
|
954
|
+
if (sessionUser) {
|
|
955
|
+
const pluginName = module.plugin_name;
|
|
956
|
+
if (sessionUser.attributes) {
|
|
957
|
+
const oldAttrs = sessionUser.attributes[pluginName] || {};
|
|
958
|
+
sessionUser.attributes[pluginName] = { ...oldAttrs, ...values };
|
|
959
|
+
} else sessionUser.attributes = { [pluginName]: values };
|
|
960
|
+
}
|
|
961
|
+
getState().processSend({
|
|
962
|
+
refresh_plugin_cfg: plugin.name,
|
|
963
|
+
tenant: db.getTenantSchema(),
|
|
964
|
+
});
|
|
965
|
+
if (module.layout) await sleep(500); // Allow other workers to reload this plugin
|
|
966
|
+
res.redirect("/auth/settings");
|
|
816
967
|
})
|
|
817
968
|
);
|
|
969
|
+
|
|
970
|
+
router.post(
|
|
971
|
+
"/user_saveconfig/:name",
|
|
972
|
+
loggedIn,
|
|
973
|
+
error_catcher(async (req, res) => {
|
|
974
|
+
const user = await User.findOne({ id: req.user?.id });
|
|
975
|
+
if (!user) return res.status(401).json({ error: req.__("Not authorized") });
|
|
976
|
+
const { name } = req.params;
|
|
977
|
+
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
978
|
+
let module = getState().plugins[plugin.name];
|
|
979
|
+
if (!module) {
|
|
980
|
+
module = getState().plugins[getState().plugin_module_names[plugin.name]];
|
|
981
|
+
}
|
|
982
|
+
const form = await module.user_config_form({
|
|
983
|
+
...(plugin.configuration || {}),
|
|
984
|
+
...(user._attributes?.layout?.config || {}),
|
|
985
|
+
});
|
|
986
|
+
const valResult = form.validate(req.body);
|
|
987
|
+
if (form.hasErrors) {
|
|
988
|
+
return res.status(400).json({ error: req.__("An error occured") });
|
|
989
|
+
}
|
|
990
|
+
const values = valResult.success;
|
|
991
|
+
values.is_user_config = true;
|
|
992
|
+
const userAttrs = user._attributes ? { ...user._attributes } : {};
|
|
993
|
+
userAttrs.layout = {
|
|
994
|
+
plugin: plugin.name,
|
|
995
|
+
config: values,
|
|
996
|
+
};
|
|
997
|
+
await user.update({ _attributes: userAttrs });
|
|
998
|
+
getState().userLayouts[req.user.email] = module.layout(
|
|
999
|
+
userAttrs.layout.config
|
|
1000
|
+
);
|
|
1001
|
+
const sessionUser = req.session?.passport?.user;
|
|
1002
|
+
if (sessionUser) {
|
|
1003
|
+
const pluginName = module.plugin_name;
|
|
1004
|
+
if (sessionUser.attributes) {
|
|
1005
|
+
const oldAttrs = sessionUser.attributes[pluginName] || {};
|
|
1006
|
+
sessionUser.attributes[pluginName] = { ...oldAttrs, ...values };
|
|
1007
|
+
} else sessionUser.attributes = { [pluginName]: values };
|
|
1008
|
+
}
|
|
1009
|
+
getState().processSend({
|
|
1010
|
+
refresh_plugin_cfg: plugin.name,
|
|
1011
|
+
tenant: db.getTenantSchema(),
|
|
1012
|
+
});
|
|
1013
|
+
res.json({ success: "ok" });
|
|
1014
|
+
})
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
router.post(
|
|
1018
|
+
"/remove_user_layout",
|
|
1019
|
+
loggedIn,
|
|
1020
|
+
error_catcher(async (req, res) => {
|
|
1021
|
+
const user = await User.findOne({ id: req.user.id });
|
|
1022
|
+
if (!user) {
|
|
1023
|
+
return res.status(401).json({ error: req.__("Not authorized") });
|
|
1024
|
+
} else if (user._attributes?.layout) {
|
|
1025
|
+
const userAttrs = { ...user._attributes };
|
|
1026
|
+
const plugin = userAttrs.layout.plugin;
|
|
1027
|
+
delete userAttrs.layout;
|
|
1028
|
+
await user.update({ _attributes: userAttrs });
|
|
1029
|
+
getState().userLayouts[req.user.email] = null;
|
|
1030
|
+
let module = getState().plugins[plugin];
|
|
1031
|
+
if (!module) {
|
|
1032
|
+
module = getState().plugins[getState().plugin_module_names[plugin]];
|
|
1033
|
+
}
|
|
1034
|
+
const pluginName = module.plugin_name;
|
|
1035
|
+
const sessionUser = req.session?.passport?.user;
|
|
1036
|
+
if (sessionUser?.attributes[pluginName])
|
|
1037
|
+
sessionUser.attributes[pluginName] = {};
|
|
1038
|
+
getState().processSend({
|
|
1039
|
+
refresh_plugin_cfg: plugin,
|
|
1040
|
+
tenant: db.getTenantSchema(),
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
res.json({ success: "ok", reload_page: true });
|
|
1044
|
+
})
|
|
1045
|
+
);
|
|
1046
|
+
|
|
818
1047
|
/**
|
|
819
1048
|
* @name get/new
|
|
820
1049
|
* @function
|
|
@@ -866,8 +1095,15 @@ router.get(
|
|
|
866
1095
|
const fullpath = path.join(location, "public", safeFile);
|
|
867
1096
|
if (fs.existsSync(fullpath))
|
|
868
1097
|
res.sendFile(fullpath, { maxAge: hasVersion ? "100d" : "1d" });
|
|
869
|
-
else
|
|
1098
|
+
else {
|
|
1099
|
+
getState().log(6, `Plugin serve public: file not found ${fullpath}`);
|
|
1100
|
+
res.status(404).send(req.__("Not found"));
|
|
1101
|
+
}
|
|
870
1102
|
} else {
|
|
1103
|
+
getState().log(
|
|
1104
|
+
6,
|
|
1105
|
+
`Plugin serve public: No location for plugin: ${plugin}`
|
|
1106
|
+
);
|
|
871
1107
|
res.status(404).send(req.__("Not found"));
|
|
872
1108
|
}
|
|
873
1109
|
})
|