@saltcorn/server 0.9.5-beta.2 → 0.9.5-beta.21

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 CHANGED
@@ -45,6 +45,7 @@ const {
45
45
  p,
46
46
  script,
47
47
  domReady,
48
+ button,
48
49
  } = require("@saltcorn/markup/tags");
49
50
  const {
50
51
  available_languages,
@@ -237,13 +238,7 @@ const loginWithJwt = async (email, password, saltcornApp, res, req) => {
237
238
  const token = jwt.sign(
238
239
  {
239
240
  sub: email,
240
- user: {
241
- id: user.id,
242
- email: user.email,
243
- role_id: user.role_id,
244
- language: user.language ? user.language : "en",
245
- disabled: user.disabled,
246
- },
241
+ user: user.session_object,
247
242
  iss: "saltcorn@saltcorn",
248
243
  aud: "saltcorn-mobile-app",
249
244
  iat: now.valueOf(),
@@ -1362,6 +1357,57 @@ const userSettings = async ({ req, res, pwform, user }) => {
1362
1357
  ),
1363
1358
  ],
1364
1359
  };
1360
+ let themeCfgCard;
1361
+ const layoutPlugin = getState().getLayoutPlugin(user);
1362
+ const modNames = getState().plugin_module_names;
1363
+ const pluginName = layoutPlugin.plugin_name;
1364
+ let safeName = pluginName;
1365
+ for (const [k, v] of Object.entries(modNames)) {
1366
+ if (v === pluginName) safeName = k;
1367
+ }
1368
+
1369
+ const hasUserConfigs =
1370
+ layoutPlugin.user_config_form &&
1371
+ (await layoutPlugin.user_config_form(
1372
+ getState().plugin_cfgs[pluginName] || {}
1373
+ )) !== null;
1374
+ themeCfgCard = {
1375
+ type: "card",
1376
+ title: req.__("Layout"),
1377
+ contents: [
1378
+ div(
1379
+ hasUserConfigs
1380
+ ? req.__("Adjust the the theme for this user")
1381
+ : req.__("The current theme has no user specific settings")
1382
+ ),
1383
+ hasUserConfigs
1384
+ ? div(
1385
+ {
1386
+ class: "mt-4",
1387
+ },
1388
+
1389
+ a(
1390
+ {
1391
+ class: "btn btn-primary",
1392
+ role: "button",
1393
+ href: `/plugins/user_configure/${encodeURIComponent(safeName)}`,
1394
+ title: req.__("Configure theme"),
1395
+ },
1396
+ req.__("Configure")
1397
+ ),
1398
+ button(
1399
+ {
1400
+ class: "btn btn-primary ms-2",
1401
+ onclick: "ajax_post('/plugins/remove_user_layout')",
1402
+ title: req.__("Remove all user specific theme settings"),
1403
+ },
1404
+ req.__("Reset")
1405
+ )
1406
+ )
1407
+ : "",
1408
+ ],
1409
+ };
1410
+
1365
1411
  return {
1366
1412
  above: [
1367
1413
  {
@@ -1434,6 +1480,7 @@ const userSettings = async ({ req, res, pwform, user }) => {
1434
1480
  ]
1435
1481
  : []),
1436
1482
  ...(apikeycard ? [apikeycard] : []),
1483
+ ...(themeCfgCard ? [themeCfgCard] : []),
1437
1484
  ],
1438
1485
  };
1439
1486
  };
package/errors.js CHANGED
@@ -37,6 +37,7 @@ module.exports =
37
37
  const createCrash = severity <= 3;
38
38
  //console.error(err.stack);
39
39
  if (!(devmode && log_sql) && createCrash) await Crash.create(err, req);
40
+ else console.error(err);
40
41
 
41
42
  if (req.xhr) {
42
43
  res
@@ -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
@@ -6,59 +6,10 @@
6
6
  * @module load_plugins
7
7
  */
8
8
  const db = require("@saltcorn/data/db");
9
- const { PluginManager } = require("live-plugin-manager");
10
9
  const { getState, getRootState } = require("@saltcorn/data/db/state");
11
10
  const Plugin = require("@saltcorn/data/models/plugin");
12
- const fs = require("fs");
13
- const proc = require("child_process");
14
- const tmp = require("tmp-promise");
15
11
 
16
- const staticDependencies = {
17
- "@saltcorn/markup": require("@saltcorn/markup"),
18
- "@saltcorn/markup/tags": require("@saltcorn/markup/tags"),
19
- "@saltcorn/markup/layout": require("@saltcorn/markup/layout"),
20
- "@saltcorn/markup/helpers": require("@saltcorn/markup/helpers"),
21
- "@saltcorn/markup/layout_utils": require("@saltcorn/markup/layout_utils"),
22
- "@saltcorn/data": require("@saltcorn/data"),
23
- "@saltcorn/data/db": require("@saltcorn/data/db"),
24
- "@saltcorn/data/utils": require("@saltcorn/data/utils"),
25
- "@saltcorn/data/db/state": require("@saltcorn/data/db/state"),
26
- "@saltcorn/data/plugin-helper": require("@saltcorn/data/plugin-helper"),
27
- "@saltcorn/data/plugin-testing": require("@saltcorn/data/plugin-testing"),
28
- "@saltcorn/data/models/field": require("@saltcorn/data/models/field"),
29
- "@saltcorn/data/models/fieldrepeat": require("@saltcorn/data/models/fieldrepeat"),
30
- "@saltcorn/data/models/table": require("@saltcorn/data/models/table"),
31
- "@saltcorn/data/models/form": require("@saltcorn/data/models/form"),
32
- "@saltcorn/data/models/discovery": require("@saltcorn/data/models/discovery"),
33
- "@saltcorn/data/models/config": require("@saltcorn/data/models/config"),
34
- "@saltcorn/data/models/library": require("@saltcorn/data/models/library"),
35
- "@saltcorn/data/models/model": require("@saltcorn/data/models/model"),
36
- "@saltcorn/data/models/model_instance": require("@saltcorn/data/models/model_instance"),
37
- "@saltcorn/data/models/notification": require("@saltcorn/data/models/notification"),
38
- "@saltcorn/data/models/role": require("@saltcorn/data/models/role"),
39
- "@saltcorn/data/models/tag": require("@saltcorn/data/models/tag"),
40
- "@saltcorn/data/models/tag_entry": require("@saltcorn/data/models/tag_entry"),
41
- "@saltcorn/data/models/view": require("@saltcorn/data/models/view"),
42
- "@saltcorn/data/models/page": require("@saltcorn/data/models/page"),
43
- "@saltcorn/data/models/file": require("@saltcorn/data/models/file"),
44
- "@saltcorn/data/models/user": require("@saltcorn/data/models/user"),
45
- "@saltcorn/data/models/layout": require("@saltcorn/data/models/layout"),
46
- "@saltcorn/data/models/expression": require("@saltcorn/data/models/expression"),
47
- "@saltcorn/data/models/workflow": require("@saltcorn/data/models/workflow"),
48
- imapflow: require("imapflow"),
49
- "node-fetch": require("node-fetch"),
50
- };
51
-
52
- /**
53
- * Create plugin manager with default list of core plugins
54
- * @type {PluginManager}
55
- */
56
- const defaultManager = new PluginManager({
57
- staticDependencies: {
58
- contractis: require("contractis"),
59
- ...staticDependencies,
60
- },
61
- });
12
+ const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
62
13
 
63
14
  /**
64
15
  * Load one plugin
@@ -68,19 +19,40 @@ const defaultManager = new PluginManager({
68
19
  */
69
20
  const loadPlugin = async (plugin, force) => {
70
21
  // load plugin
71
- const res = await requirePlugin(plugin, force);
22
+ const loader = new PluginInstaller(plugin);
23
+ const res = await loader.install(force);
72
24
  const configuration =
73
25
  typeof plugin.configuration === "string"
74
26
  ? JSON.parse(plugin.configuration)
75
27
  : plugin.configuration;
76
- // register plugin
77
- getState().registerPlugin(
78
- res.plugin_module.plugin_name || plugin.name,
79
- res.plugin_module,
80
- configuration,
81
- res.location,
82
- res.name
83
- );
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
+ getState().log(
39
+ 3,
40
+ `Error loading plugin ${plugin.name}: ${error.message || error}`
41
+ );
42
+ if (force) {
43
+ // remove the install dir and try again
44
+ await loader.remove();
45
+ await loader.install(force);
46
+ getState().registerPlugin(
47
+ res.plugin_module.plugin_name || plugin.name,
48
+ res.plugin_module,
49
+ configuration,
50
+ res.location,
51
+ res.name
52
+ );
53
+ }
54
+ }
55
+ if (res.plugin_module.user_config_form) getState().refreshUserLayouts();
84
56
  if (res.plugin_module.onLoad) {
85
57
  try {
86
58
  await res.plugin_module.onLoad(plugin.configuration);
@@ -91,105 +63,49 @@ const loadPlugin = async (plugin, force) => {
91
63
  return res;
92
64
  };
93
65
 
94
- /**
95
- * Git pull or clone
96
- * @param plugin
97
- */
98
- const gitPullOrClone = async (plugin) => {
99
- await fs.promises.mkdir("git_plugins", { recursive: true });
100
- let keyfnm,
101
- setKey = `-c core.sshCommand="ssh -oBatchMode=yes -o 'StrictHostKeyChecking no'" `;
102
- if (plugin.deploy_private_key) {
103
- keyfnm = await tmp.tmpName();
104
- await fs.promises.writeFile(
105
- keyfnm,
106
- plugin.deploy_private_key.replace(/[\r]+/g, "") + "\n",
107
- {
108
- mode: 0o600,
109
- encoding: "ascii",
110
- }
111
- );
112
- setKey = `-c core.sshCommand="ssh -oBatchMode=yes -o 'StrictHostKeyChecking no' -i ${keyfnm}" `;
113
- }
114
- const dir = `git_plugins/${plugin.name}`;
115
- if (fs.existsSync(dir)) {
116
- proc.execSync(`git ${setKey} -C ${dir} pull`);
117
- } else {
118
- proc.execSync(`git ${setKey} clone ${plugin.location} ${dir}`);
119
- }
120
- if (plugin.deploy_private_key) await fs.promises.unlink(keyfnm);
121
- return dir;
122
- };
123
66
  /**
124
67
  * Install plugin
125
68
  * @param plugin - plugin name
126
69
  * @param force - force flag
127
- * @param manager - plugin manager
128
70
  * @returns {Promise<{plugin_module: *}|{plugin_module: any}>}
129
71
  */
130
- const requirePlugin = async (plugin, force, manager = defaultManager) => {
131
- const installed_plugins = (await manager.list()).map((p) => p.name);
132
- // todo as idea is to make list of mandatory plugins configurable
133
- if (
134
- ["@saltcorn/base-plugin", "@saltcorn/sbadmin2"].includes(plugin.location)
135
- ) {
136
- return { plugin_module: require(plugin.location) };
137
- } else if (plugin.source === "npm") {
138
- if (force || !installed_plugins.includes(plugin.location)) {
139
- const plinfo = await manager.install(plugin.location, plugin.version);
140
- return { plugin_module: manager.require(plugin.location), ...plinfo };
141
- } else {
142
- const plinfo = manager.getInfo(plugin.location);
143
- return { plugin_module: manager.require(plugin.location), ...plinfo };
144
- }
145
- } else if (plugin.source === "local") {
146
- const plinfo = await manager.installFromPath(plugin.location, {
147
- force: true,
148
- });
149
- return { plugin_module: manager.require(plugin.name), ...plinfo };
150
- } else if (plugin.source === "git") {
151
- const loc = await gitPullOrClone(plugin);
152
- const plinfo = await manager.installFromPath(loc, {
153
- force: true,
154
- });
155
- return { plugin_module: manager.require(plugin.name), ...plinfo };
156
- } else if (plugin.source === "github") {
157
- if (force || !installed_plugins.includes(plugin.location)) {
158
- const plinfo = await manager.installFromGithub(plugin.location, {
159
- force: true,
160
- });
161
- return { plugin_module: manager.require(plugin.name), ...plinfo };
162
- } else {
163
- const plinfo = manager.getInfo(plugin.location);
164
- return { plugin_module: manager.require(plugin.location), ...plinfo };
165
- }
166
- } else throw new Error("Unknown plugin source: " + plugin.source);
72
+ const requirePlugin = async (plugin, force) => {
73
+ const loader = new PluginInstaller(plugin);
74
+ return await loader.install(force);
167
75
  };
76
+
168
77
  /**
169
78
  * Load all plugins
170
79
  * @returns {Promise<void>}
171
80
  */
172
- const loadAllPlugins = async () => {
81
+ const loadAllPlugins = async (force) => {
173
82
  await getState().refresh(true);
174
83
  const plugins = await db.select("_sc_plugins");
175
84
  for (const plugin of plugins) {
176
85
  try {
177
- await loadPlugin(plugin);
86
+ await loadPlugin(plugin, force);
178
87
  } catch (e) {
179
88
  console.error(e);
180
89
  }
181
90
  }
91
+ await getState().refreshUserLayouts();
182
92
  await getState().refresh(true);
183
93
  };
94
+
184
95
  /**
185
96
  * Load Plugin and its dependencies and save into local installation
186
97
  * @param plugin
187
98
  * @param force
188
99
  * @param noSignalOrDB
189
- * @param manager - optional plugin manager
100
+ * @param __ translation function
190
101
  * @returns {Promise<void>}
191
102
  */
192
- const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
103
+ const loadAndSaveNewPlugin = async (
104
+ plugin,
105
+ force,
106
+ noSignalOrDB,
107
+ __ = (str) => str
108
+ ) => {
193
109
  const tenants_unsafe_plugins = getRootState().getConfig(
194
110
  "tenants_unsafe_plugins",
195
111
  false
@@ -215,11 +131,10 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
215
131
  return;
216
132
  }
217
133
  }
218
- const { version, plugin_module, location } = await requirePlugin(
219
- plugin,
220
- force,
221
- manager
222
- );
134
+ const msgs = [];
135
+ const loader = new PluginInstaller(plugin);
136
+ const { version, plugin_module, location, loadedWithReload } =
137
+ await loader.install(force);
223
138
 
224
139
  // install dependecies
225
140
  for (const loc of plugin_module.dependencies || []) {
@@ -232,13 +147,43 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
232
147
  );
233
148
  }
234
149
  }
235
- getState().registerPlugin(
236
- plugin_module.plugin_name || plugin.name,
237
- plugin_module,
238
- plugin.configuration,
239
- location,
240
- plugin.name
241
- );
150
+ let registeredWithReload = false;
151
+ try {
152
+ getState().registerPlugin(
153
+ plugin_module.plugin_name || plugin.name,
154
+ plugin_module,
155
+ plugin.configuration,
156
+ location,
157
+ plugin.name
158
+ );
159
+ } catch (error) {
160
+ if (force) {
161
+ getState().log(
162
+ 2,
163
+ `Error registering plugin ${plugin.name}. Removing and trying again.`
164
+ );
165
+ await loader.remove();
166
+ await loader.install(force);
167
+ getState().registerPlugin(
168
+ plugin_module.plugin_name || plugin.name,
169
+ plugin_module,
170
+ plugin.configuration,
171
+ location,
172
+ plugin.name
173
+ );
174
+ registeredWithReload = true;
175
+ } else {
176
+ throw error;
177
+ }
178
+ }
179
+ if (loadedWithReload || registeredWithReload) {
180
+ msgs.push(
181
+ __(
182
+ "The plugin was corrupted and had to be repaired. We recommend restarting your server.",
183
+ plugin.name
184
+ )
185
+ );
186
+ }
242
187
  if (plugin_module.onLoad) {
243
188
  try {
244
189
  await plugin_module.onLoad(plugin.configuration);
@@ -252,9 +197,10 @@ const loadAndSaveNewPlugin = async (plugin, force, noSignalOrDB, manager) => {
252
197
  getState().processSend({
253
198
  installPlugin: plugin,
254
199
  tenant: db.getTenantSchema(),
255
- force,
200
+ force: false, // okay ??
256
201
  });
257
202
  }
203
+ return msgs;
258
204
  };
259
205
 
260
206
  module.exports = {
@@ -262,5 +208,4 @@ module.exports = {
262
208
  loadAllPlugins,
263
209
  loadPlugin,
264
210
  requirePlugin,
265
- staticDependencies,
266
211
  };
package/locales/en.json CHANGED
@@ -1384,5 +1384,35 @@
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"
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
+ "%s code page": "%s code page",
1396
+ "Constants and function code": "Constants and function code",
1397
+ "Delete code page": "Delete code page",
1398
+ "Adjust the the theme for this user": "Adjust the the theme for this user",
1399
+ "Configure theme": "Configure theme",
1400
+ "Remove all user specific theme settings": "Remove all user specific theme settings",
1401
+ "Configure %s Plugin for %s": "Configure %s Plugin for %s",
1402
+ "The current theme has no user specific settings": "The current theme has no user specific settings",
1403
+ "Some themes support only one level of menu nesting.": "Some themes support only one level of menu nesting.",
1404
+ "Apple Team ID": "Apple Team ID",
1405
+ "Please enter your Apple Team ID": "Please enter your Apple Team ID",
1406
+ "To see changes for '%s' in show-if-formulas, users need to relogin": "To see changes for '%s' in show-if-formulas, users need to relogin",
1407
+ "Server host": "Server host",
1408
+ "Username": "Username",
1409
+ "Port": "Port",
1410
+ "All tenants": "All tenants",
1411
+ "Also backup all tenants": "Also backup all tenants",
1412
+ "Retain local directory": "Retain local directory",
1413
+ "Retain a local backup copy in this directory (optional)": "Retain a local backup copy in this directory (optional)",
1414
+ "Do not wrap response in a success object": "Do not wrap response in a success object",
1415
+ "Use table description instead of name as header": "Use table description instead of name as header",
1416
+ "Description header": "Description header",
1417
+ "Lazy load views": "Lazy load views"
1388
1418
  }