@saltcorn/server 1.1.0-beta.9 → 1.1.1-beta.0
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 +1 -1
- package/auth/roleadmin.js +10 -2
- package/help/{Cordova Builder.tmd → Capacitor Builder.tmd } +1 -1
- package/help/Configuration keys.tmd +5 -0
- package/help/index.js +2 -0
- package/load_plugins.js +12 -5
- package/locales/en.json +30 -1
- package/locales/pl.json +19 -2
- package/markup/admin.js +7 -3
- package/package.json +9 -9
- package/public/codemirror.css +33 -0
- package/public/flatpickr-dark.css +795 -0
- package/public/gridedit.js +1 -1
- package/public/mermaid.min.js +1077 -792
- package/public/saltcorn-common.js +93 -44
- package/public/saltcorn.css +27 -6
- package/public/saltcorn.js +60 -22
- package/routes/actions.js +1041 -4
- package/routes/admin.js +91 -81
- package/routes/api.js +51 -0
- package/routes/config.js +39 -25
- package/routes/eventlog.js +41 -1
- package/routes/fields.js +8 -2
- package/routes/homepage.js +13 -3
- package/routes/list.js +17 -1
- package/routes/plugins.js +7 -2
- package/routes/registry.js +45 -3
- package/routes/tables.js +58 -20
- package/routes/tenant.js +10 -2
- package/routes/viewedit.js +8 -8
- package/wrapper.js +3 -1
package/routes/plugins.js
CHANGED
|
@@ -57,7 +57,11 @@ const fs = require("fs");
|
|
|
57
57
|
const path = require("path");
|
|
58
58
|
const { get_latest_npm_version } = require("@saltcorn/data/models/config");
|
|
59
59
|
const { flash_restart } = require("../markup/admin.js");
|
|
60
|
-
const {
|
|
60
|
+
const {
|
|
61
|
+
sleep,
|
|
62
|
+
removeNonWordChars,
|
|
63
|
+
getFetchProxyOptions,
|
|
64
|
+
} = require("@saltcorn/data/utils");
|
|
61
65
|
const { loadAllPlugins } = require("../load_plugins");
|
|
62
66
|
const npmFetch = require("npm-registry-fetch");
|
|
63
67
|
const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
|
|
@@ -609,7 +613,8 @@ router.get(
|
|
|
609
613
|
} else {
|
|
610
614
|
try {
|
|
611
615
|
const pkgInfo = await npmFetch.json(
|
|
612
|
-
`https://registry.npmjs.org/${plugin.location}
|
|
616
|
+
`https://registry.npmjs.org/${plugin.location}`,
|
|
617
|
+
getFetchProxyOptions()
|
|
613
618
|
);
|
|
614
619
|
if (!pkgInfo?.versions)
|
|
615
620
|
throw new Error(req.__("Unable to fetch versions"));
|
package/routes/registry.js
CHANGED
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
domReady,
|
|
8
8
|
a,
|
|
9
9
|
div,
|
|
10
|
+
h4,
|
|
10
11
|
i,
|
|
11
12
|
text,
|
|
12
13
|
button,
|
|
@@ -40,6 +41,7 @@ const {
|
|
|
40
41
|
install_pack,
|
|
41
42
|
} = require("@saltcorn/admin-models/models/pack");
|
|
42
43
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
44
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
43
45
|
/**
|
|
44
46
|
* @type {object}
|
|
45
47
|
* @const
|
|
@@ -79,7 +81,17 @@ router.get(
|
|
|
79
81
|
{},
|
|
80
82
|
{ orderBy: "name", nocase: true }
|
|
81
83
|
);
|
|
82
|
-
|
|
84
|
+
const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
|
|
85
|
+
|
|
86
|
+
const all_configs_obj = await getState().getAllConfigOrDefaults();
|
|
87
|
+
const all_configs = Object.entries(all_configs_obj)
|
|
88
|
+
.map(([name, v]) => ({
|
|
89
|
+
...v,
|
|
90
|
+
name,
|
|
91
|
+
}))
|
|
92
|
+
.filter((c) => isRoot || !c.root_only);
|
|
93
|
+
|
|
94
|
+
let tables, views, pages, triggers, configs;
|
|
83
95
|
if (q) {
|
|
84
96
|
const qlower = q.toLowerCase();
|
|
85
97
|
const includesQ = (s) => s.toLowerCase().includes(qlower);
|
|
@@ -100,11 +112,13 @@ router.get(
|
|
|
100
112
|
const pack = await trigger_pack(t);
|
|
101
113
|
return includesQ(JSON.stringify(pack));
|
|
102
114
|
});
|
|
115
|
+
configs = all_configs.filter((c) => includesQ(JSON.stringify(c)));
|
|
103
116
|
} else {
|
|
104
117
|
tables = all_tables;
|
|
105
118
|
views = all_views;
|
|
106
119
|
pages = all_pages;
|
|
107
120
|
triggers = all_triggers;
|
|
121
|
+
configs = all_configs;
|
|
108
122
|
}
|
|
109
123
|
const li_link = (etype1, ename1) =>
|
|
110
124
|
li(
|
|
@@ -124,7 +138,7 @@ router.get(
|
|
|
124
138
|
action: `/registry-editor?etype=${etype}&ename=${encodeURIComponent(
|
|
125
139
|
ename
|
|
126
140
|
)}${qlink}`,
|
|
127
|
-
|
|
141
|
+
formStyle: "vert",
|
|
128
142
|
values: { regval: JSON.stringify(jsonVal, null, 2) },
|
|
129
143
|
fields: [
|
|
130
144
|
{
|
|
@@ -177,6 +191,14 @@ router.get(
|
|
|
177
191
|
const ppack = await page_pack(all_pages.find((v) => v.name === ename));
|
|
178
192
|
edContents = renderForm(mkForm(ppack), req.csrfToken());
|
|
179
193
|
break;
|
|
194
|
+
case "config":
|
|
195
|
+
const config = all_configs.find((t) => t.name === ename);
|
|
196
|
+
edContents =
|
|
197
|
+
h4(config.label) +
|
|
198
|
+
(config.blurb || "") +
|
|
199
|
+
(config.sublabel || "") +
|
|
200
|
+
renderForm(mkForm(config.value), req.csrfToken());
|
|
201
|
+
break;
|
|
180
202
|
case "trigger":
|
|
181
203
|
const trigger = all_triggers.find((t) => t.name === ename);
|
|
182
204
|
const trpack = await trigger_pack(trigger);
|
|
@@ -282,6 +304,16 @@ router.get(
|
|
|
282
304
|
triggers.map((t) => li_link("trigger", t.name))
|
|
283
305
|
)
|
|
284
306
|
)
|
|
307
|
+
),
|
|
308
|
+
li(
|
|
309
|
+
details(
|
|
310
|
+
{ open: q || etype === "CONFIG" }, //
|
|
311
|
+
summary("Configuration"),
|
|
312
|
+
ul(
|
|
313
|
+
{ class: "ps-3" },
|
|
314
|
+
configs.map((t) => li_link("config", t.name))
|
|
315
|
+
)
|
|
316
|
+
)
|
|
285
317
|
)
|
|
286
318
|
)
|
|
287
319
|
),
|
|
@@ -309,7 +341,14 @@ router.post(
|
|
|
309
341
|
const qlink = q ? `&q=${encodeURIComponent(q)}` : "";
|
|
310
342
|
|
|
311
343
|
const entVal = JSON.parse(req.body.regval);
|
|
312
|
-
let pack = {
|
|
344
|
+
let pack = {
|
|
345
|
+
plugins: [],
|
|
346
|
+
tables: [],
|
|
347
|
+
views: [],
|
|
348
|
+
pages: [],
|
|
349
|
+
triggers: [],
|
|
350
|
+
config: {},
|
|
351
|
+
};
|
|
313
352
|
|
|
314
353
|
switch (etype) {
|
|
315
354
|
case "table":
|
|
@@ -324,6 +363,9 @@ router.post(
|
|
|
324
363
|
case "trigger":
|
|
325
364
|
pack.triggers = [entVal];
|
|
326
365
|
break;
|
|
366
|
+
case "config":
|
|
367
|
+
pack.config[ename] = entVal;
|
|
368
|
+
break;
|
|
327
369
|
}
|
|
328
370
|
await install_pack(pack);
|
|
329
371
|
res.redirect(
|
package/routes/tables.js
CHANGED
|
@@ -56,7 +56,7 @@ const {
|
|
|
56
56
|
} = require("@saltcorn/data/models/discovery");
|
|
57
57
|
const { getState } = require("@saltcorn/data/db/state");
|
|
58
58
|
const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
|
|
59
|
-
const { tablesList, viewsList } = require("./common_lists");
|
|
59
|
+
const { tablesList, viewsList, getTriggerList } = require("./common_lists");
|
|
60
60
|
const {
|
|
61
61
|
InvalidConfiguration,
|
|
62
62
|
removeAllWhiteSpace,
|
|
@@ -757,6 +757,8 @@ router.get(
|
|
|
757
757
|
const triggers = table.id ? Trigger.find({ table_id: table.id }) : [];
|
|
758
758
|
triggers.sort(comparingCaseInsensitive("name"));
|
|
759
759
|
let fieldCard;
|
|
760
|
+
const nPrimaryKeys = fields.filter((f) => f.primary_key).length;
|
|
761
|
+
|
|
760
762
|
if (fields.length === 0) {
|
|
761
763
|
fieldCard = [
|
|
762
764
|
h4(req.__(`No fields defined in %s table`, table.name)),
|
|
@@ -818,28 +820,25 @@ router.get(
|
|
|
818
820
|
{ hover: true }
|
|
819
821
|
);
|
|
820
822
|
fieldCard = [
|
|
823
|
+
nPrimaryKeys > 1 &&
|
|
824
|
+
div(
|
|
825
|
+
{ class: "alert alert-danger", role: "alert" },
|
|
826
|
+
i({ class: "fas fa-exclamation-triangle" }),
|
|
827
|
+
"This table has composite primary keys which is not supported in Saltcorn. A procedure to introduce a single autoincrementing primary key is available.",
|
|
828
|
+
post_btn(
|
|
829
|
+
`/table/repair-composite-primary/${table.id}`,
|
|
830
|
+
"Add autoincrementing primary key",
|
|
831
|
+
req.csrfToken(),
|
|
832
|
+
{ btnClass: "btn-danger" }
|
|
833
|
+
)
|
|
834
|
+
),
|
|
835
|
+
|
|
821
836
|
tableHtml,
|
|
822
837
|
inbound_refs.length > 0
|
|
823
838
|
? req.__("Inbound keys: ") +
|
|
824
839
|
inbound_refs.map((tnm) => link(`/table/${tnm}`, tnm)).join(", ") +
|
|
825
840
|
"<br>"
|
|
826
841
|
: "",
|
|
827
|
-
triggers.length
|
|
828
|
-
? req.__("Table triggers: ") +
|
|
829
|
-
triggers
|
|
830
|
-
.map((t) =>
|
|
831
|
-
link(
|
|
832
|
-
`/actions/configure/${
|
|
833
|
-
t.id
|
|
834
|
-
}?on_done_redirect=${encodeURIComponent(
|
|
835
|
-
`table/${table.name}`
|
|
836
|
-
)}`,
|
|
837
|
-
t.name
|
|
838
|
-
)
|
|
839
|
-
)
|
|
840
|
-
.join(", ") +
|
|
841
|
-
"<br>"
|
|
842
|
-
: "",
|
|
843
842
|
!table.external &&
|
|
844
843
|
!table.provider_name &&
|
|
845
844
|
a(
|
|
@@ -851,7 +850,8 @@ router.get(
|
|
|
851
850
|
),
|
|
852
851
|
];
|
|
853
852
|
}
|
|
854
|
-
|
|
853
|
+
let viewCard;
|
|
854
|
+
let triggerCard = "";
|
|
855
855
|
if (fields.length > 0) {
|
|
856
856
|
const views = await View.find(
|
|
857
857
|
table.id ? { table_id: table.id } : { exttable_name: table.name }
|
|
@@ -884,6 +884,25 @@ router.get(
|
|
|
884
884
|
req.__("Create view")
|
|
885
885
|
),
|
|
886
886
|
};
|
|
887
|
+
|
|
888
|
+
triggerCard = {
|
|
889
|
+
type: "card",
|
|
890
|
+
id: "table-triggers",
|
|
891
|
+
title: req.__("Triggers on table"),
|
|
892
|
+
contents:
|
|
893
|
+
(triggers.length
|
|
894
|
+
? await getTriggerList(triggers, req)
|
|
895
|
+
: p("Triggers run actions in response to events on this table")) +
|
|
896
|
+
a(
|
|
897
|
+
{
|
|
898
|
+
href: `/actions/new?table=${encodeURIComponent(
|
|
899
|
+
table.name
|
|
900
|
+
)}&on_done_redirect=${encodeURIComponent(`table/${table.name}`)}`,
|
|
901
|
+
class: "btn btn-primary",
|
|
902
|
+
},
|
|
903
|
+
req.__("Create trigger")
|
|
904
|
+
),
|
|
905
|
+
};
|
|
887
906
|
}
|
|
888
907
|
const models = await Model.find({ table_id: table.id });
|
|
889
908
|
const modelCard = div(
|
|
@@ -1078,6 +1097,7 @@ router.get(
|
|
|
1078
1097
|
]
|
|
1079
1098
|
: []),
|
|
1080
1099
|
...(viewCard ? [viewCard] : []),
|
|
1100
|
+
...(triggerCard ? [triggerCard] : []),
|
|
1081
1101
|
{
|
|
1082
1102
|
type: "card",
|
|
1083
1103
|
title: req.__("Edit table properties"),
|
|
@@ -1111,7 +1131,7 @@ router.post(
|
|
|
1111
1131
|
const v = req.body;
|
|
1112
1132
|
if (typeof v.id === "undefined" && typeof v.external === "undefined") {
|
|
1113
1133
|
// insert
|
|
1114
|
-
v.name = v.name.trim()
|
|
1134
|
+
v.name = v.name.trim();
|
|
1115
1135
|
const { name, ...rest } = v;
|
|
1116
1136
|
const alltables = await Table.find({});
|
|
1117
1137
|
const existing_tables = [
|
|
@@ -1162,6 +1182,7 @@ router.post(
|
|
|
1162
1182
|
let notify = "";
|
|
1163
1183
|
if (!rest.versioned) rest.versioned = false;
|
|
1164
1184
|
if (!rest.has_sync_info) rest.has_sync_info = false;
|
|
1185
|
+
rest.is_user_group = !!rest.is_user_group;
|
|
1165
1186
|
if (rest.ownership_field_id === "_formula") {
|
|
1166
1187
|
rest.ownership_field_id = null;
|
|
1167
1188
|
const fmlValidRes = expressionValidator(rest.ownership_formula);
|
|
@@ -1922,7 +1943,7 @@ router.post(
|
|
|
1922
1943
|
const table = Table.findOne({ name });
|
|
1923
1944
|
|
|
1924
1945
|
try {
|
|
1925
|
-
await table.deleteRows({}, req.user);
|
|
1946
|
+
await table.deleteRows({}, req.user, true);
|
|
1926
1947
|
req.flash("success", req.__("Deleted all rows"));
|
|
1927
1948
|
} catch (e) {
|
|
1928
1949
|
req.flash("error", e.message);
|
|
@@ -2074,3 +2095,20 @@ router.post(
|
|
|
2074
2095
|
respondWorkflow(table, workflow, wfres, req, res);
|
|
2075
2096
|
})
|
|
2076
2097
|
);
|
|
2098
|
+
|
|
2099
|
+
router.post(
|
|
2100
|
+
"/repair-composite-primary/:id",
|
|
2101
|
+
isAdmin,
|
|
2102
|
+
error_catcher(async (req, res) => {
|
|
2103
|
+
const { id } = req.params;
|
|
2104
|
+
|
|
2105
|
+
const table = Table.findOne({ id });
|
|
2106
|
+
if (!table) {
|
|
2107
|
+
req.flash("error", `Table not found`);
|
|
2108
|
+
res.redirect(`/table`);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
await table.repairCompositePrimary();
|
|
2112
|
+
res.redirect(`/table/${table.id}`);
|
|
2113
|
+
})
|
|
2114
|
+
);
|
package/routes/tenant.js
CHANGED
|
@@ -355,7 +355,12 @@ router.post(
|
|
|
355
355
|
...tenant_letsencrypt_sites,
|
|
356
356
|
]);
|
|
357
357
|
if (req.user?.role_id === 1) {
|
|
358
|
-
req.flash(
|
|
358
|
+
req.flash(
|
|
359
|
+
"success",
|
|
360
|
+
req.__(
|
|
361
|
+
"Tenant created. Certificate will be acquired on first visit."
|
|
362
|
+
)
|
|
363
|
+
);
|
|
359
364
|
res.redirect("/tenant/list");
|
|
360
365
|
return;
|
|
361
366
|
}
|
|
@@ -416,6 +421,7 @@ router.get(
|
|
|
416
421
|
return;
|
|
417
422
|
}
|
|
418
423
|
const tens = await db.select("_sc_tenants");
|
|
424
|
+
const locale = getState().getConfig("default_locale", "en");
|
|
419
425
|
send_infoarch_page({
|
|
420
426
|
res,
|
|
421
427
|
req,
|
|
@@ -442,7 +448,8 @@ router.get(
|
|
|
442
448
|
},
|
|
443
449
|
{
|
|
444
450
|
label: req.__("Created"),
|
|
445
|
-
key: (r) =>
|
|
451
|
+
key: (r) =>
|
|
452
|
+
r.created ? localeDateTime(r.created, {}, locale) : "",
|
|
446
453
|
},
|
|
447
454
|
{
|
|
448
455
|
label: req.__("Information"),
|
|
@@ -486,6 +493,7 @@ const tenant_settings_form = (req) =>
|
|
|
486
493
|
"tenant_template",
|
|
487
494
|
"tenant_baseurl",
|
|
488
495
|
"tenant_create_unauth_redirect",
|
|
496
|
+
"tenant_inherit_cfgs",
|
|
489
497
|
{ section_header: req.__("Tenant application capabilities") },
|
|
490
498
|
"tenants_install_git",
|
|
491
499
|
"tenants_set_npm_modules",
|
package/routes/viewedit.js
CHANGED
|
@@ -151,14 +151,6 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
151
151
|
"The view name is part of the URL when it is shown alone."
|
|
152
152
|
),
|
|
153
153
|
}),
|
|
154
|
-
new Field({
|
|
155
|
-
label: req.__("Description"),
|
|
156
|
-
name: "description",
|
|
157
|
-
type: "String",
|
|
158
|
-
sublabel: req.__(
|
|
159
|
-
"Description allows you to give more information about the view."
|
|
160
|
-
),
|
|
161
|
-
}),
|
|
162
154
|
new Field({
|
|
163
155
|
label: req.__("View pattern"),
|
|
164
156
|
name: "viewtemplate",
|
|
@@ -200,6 +192,14 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
|
|
|
200
192
|
required: true,
|
|
201
193
|
options: roles.map((r) => ({ value: r.id, label: r.role })),
|
|
202
194
|
}),
|
|
195
|
+
new Field({
|
|
196
|
+
label: req.__("Description"),
|
|
197
|
+
name: "description",
|
|
198
|
+
type: "String",
|
|
199
|
+
sublabel: req.__(
|
|
200
|
+
"Description allows you to give more information about the view."
|
|
201
|
+
),
|
|
202
|
+
}),
|
|
203
203
|
new Field({
|
|
204
204
|
name: "page_title",
|
|
205
205
|
label: req.__("Page title"),
|
package/wrapper.js
CHANGED
|
@@ -193,7 +193,9 @@ const get_headers = (req, version_tag, description, extras = []) => {
|
|
|
193
193
|
state.logLevel
|
|
194
194
|
}, _sc_globalCsrf = "${req.csrfToken()}", _sc_version_tag = "${version_tag}"${
|
|
195
195
|
locale ? `, _sc_locale = "${locale}"` : ""
|
|
196
|
-
}
|
|
196
|
+
}, _sc_lightmode = ${JSON.stringify(
|
|
197
|
+
state.getLightDarkMode?.(req.user) || "light"
|
|
198
|
+
)};</script>`,
|
|
197
199
|
},
|
|
198
200
|
{ css: `/static_assets/${version_tag}/saltcorn.css` },
|
|
199
201
|
{ script: `/static_assets/${version_tag}/saltcorn-common.js` },
|