@saltcorn/server 0.9.5-beta.2 → 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/auth/routes.js +54 -7
- package/errors.js +1 -0
- package/help/Cordova Builder.tmd +13 -0
- package/load_plugins.js +89 -144
- package/locales/en.json +30 -1
- package/locales/ru.json +1134 -1101
- package/package.json +16 -13
- package/public/log_viewer_utils.js +17 -1
- package/public/saltcorn-common.js +154 -24
- package/public/saltcorn.css +4 -0
- package/public/saltcorn.js +67 -46
- package/restart_watcher.js +2 -0
- package/routes/actions.js +17 -1
- package/routes/admin.js +389 -20
- package/routes/api.js +4 -1
- package/routes/common_lists.js +1 -1
- package/routes/fields.js +13 -8
- package/routes/homepage.js +6 -3
- package/routes/menu.js +12 -3
- package/routes/pageedit.js +1 -1
- package/routes/plugins.js +265 -29
- package/routes/search.js +28 -2
- package/routes/tables.js +4 -0
- package/serve.js +1 -1
- package/tests/page.test.js +11 -1
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
|
@@ -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
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
131
|
-
const
|
|
132
|
-
|
|
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
|
|
100
|
+
* @param __ translation function
|
|
190
101
|
* @returns {Promise<void>}
|
|
191
102
|
*/
|
|
192
|
-
const loadAndSaveNewPlugin = async (
|
|
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
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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,34 @@
|
|
|
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"
|
|
1388
1417
|
}
|