@saltcorn/server 0.7.3 → 0.7.4-beta.2

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 CHANGED
@@ -693,7 +693,8 @@ router.post(
693
693
  } = form.values;
694
694
  if (id) {
695
695
  try {
696
- await db.update("users", { email, role_id, ...rest }, id);
696
+ const u = await User.findOne({ id });
697
+ await u.update({ email, role_id, ...rest });
697
698
  req.flash("success", req.__(`User %s saved`, email));
698
699
  } catch (e) {
699
700
  req.flash("error", req.__(`Error editing user: %s`, e.message));
package/auth/routes.js CHANGED
@@ -199,8 +199,7 @@ const getAuthLinks = (current, noMethods) => {
199
199
  return links;
200
200
  };
201
201
 
202
- const loginWithJwt = async (req, res) => {
203
- const { email, password } = req.query;
202
+ const loginWithJwt = async (email, password, res) => {
204
203
  const user = await User.findOne({ email });
205
204
  if (user && user.checkPassword(password)) {
206
205
  const now = new Date();
@@ -208,7 +207,13 @@ const loginWithJwt = async (req, res) => {
208
207
  const token = jwt.sign(
209
208
  {
210
209
  sub: email,
211
- role_id: user.role_id,
210
+ user: {
211
+ id: user.id,
212
+ email: user.email,
213
+ role_id: user.role_id,
214
+ language: user.language ? user.language : "en",
215
+ disabled: user.disabled,
216
+ },
212
217
  iss: "saltcorn@saltcorn",
213
218
  aud: "saltcorn-mobile-app",
214
219
  iat: now.valueOf(),
@@ -217,6 +222,10 @@ const loginWithJwt = async (req, res) => {
217
222
  );
218
223
  if (!user.last_mobile_login) await user.updateLastMobileLogin(now);
219
224
  res.json(token);
225
+ } else {
226
+ res.json({
227
+ alerts: [{ type: "danger", msg: "Incorrect user or password" }],
228
+ });
220
229
  }
221
230
  };
222
231
 
@@ -326,7 +335,7 @@ router.get(
326
335
  if (result.error) req.flash("danger", result.error);
327
336
  else if (result) {
328
337
  req.flash("success", req.__("Email verified"));
329
- const u = await User.findOne({ email });
338
+ const u = await User.findForSession({ email });
330
339
  if (u) u.relogin(req);
331
340
  }
332
341
  res.redirect("/");
@@ -461,11 +470,11 @@ router.get(
461
470
  form,
462
471
  {},
463
472
  restore +
464
- script(
465
- domReady(
466
- `$('form.create-first-user button[type=submit]').click(function(){press_store_button(this)})`
467
- )
473
+ script(
474
+ domReady(
475
+ `$('form.create-first-user button[type=submit]').click(function(){press_store_button(this)})`
468
476
  )
477
+ )
469
478
  );
470
479
  } else {
471
480
  req.flash("danger", req.__("Users already present"));
@@ -494,7 +503,7 @@ router.post(
494
503
  );
495
504
  if (err) req.flash("error", err);
496
505
  else req.flash("success", req.__("Successfully restored backup"));
497
- fs.unlink(newPath, function () {});
506
+ fs.unlink(newPath, function () { });
498
507
  res.redirect(`/auth/login`);
499
508
  } else {
500
509
  req.flash("danger", req.__("Users already present"));
@@ -527,12 +536,7 @@ router.post(
527
536
  const { email, password } = form.values;
528
537
  const u = await User.create({ email, password, role_id: 1 });
529
538
  req.login(
530
- {
531
- email: u.email,
532
- id: u.id,
533
- role_id: u.role_id,
534
- tenant: db.getTenantSchema(),
535
- },
539
+ u.session_object,
536
540
  function (err) {
537
541
  if (!err) {
538
542
  Trigger.emitEvent("Login", null, u);
@@ -637,12 +641,7 @@ const getNewUserForm = async (new_user_view_name, req, askEmail) => {
637
641
  */
638
642
  const signup_login_with_user = (u, req, res) =>
639
643
  req.login(
640
- {
641
- email: u.email,
642
- id: u.id,
643
- role_id: u.role_id,
644
- tenant: db.getTenantSchema(),
645
- },
644
+ u.session_object,
646
645
  function (err) {
647
646
  if (!err) {
648
647
  Trigger.emitEvent("Login", null, u);
@@ -900,8 +899,8 @@ router.post(
900
899
  } else {
901
900
  const u = await User.create({ email, password });
902
901
  await send_verification_email(u, req);
903
-
904
- signup_login_with_user(u, req, res);
902
+ if (req.smr) await loginWithJwt(email, password, res);
903
+ else signup_login_with_user(u, req, res);
905
904
  }
906
905
  }
907
906
  })
@@ -921,7 +920,7 @@ function handler(req, res) {
921
920
  req.flash(
922
921
  "error",
923
922
  "You've made too many failed attempts in a short period of time, please try again " +
924
- moment(req.rateLimit.resetTime).fromNow()
923
+ moment(req.rateLimit.resetTime).fromNow()
925
924
  );
926
925
  res.redirect("/auth/login"); // brute force protection triggered, send them back to the login page
927
926
  }
@@ -1008,7 +1007,8 @@ router.get(
1008
1007
  error_catcher(async (req, res, next) => {
1009
1008
  const { method } = req.params;
1010
1009
  if (method === "jwt") {
1011
- await loginWithJwt(req, res);
1010
+ const { email, password } = req.query;
1011
+ await loginWithJwt(email, password, res);
1012
1012
  } else {
1013
1013
  const auth = getState().auth_methods[method];
1014
1014
  if (auth) {
@@ -1184,7 +1184,7 @@ const userSettings = async ({ req, res, pwform, user }) => {
1184
1184
  div(
1185
1185
  user.api_token
1186
1186
  ? span({ class: "me-1" }, req.__("API token for this user: ")) +
1187
- code(user.api_token)
1187
+ code(user.api_token)
1188
1188
  : req.__("No API token issued")
1189
1189
  ),
1190
1190
  // button for reset or generate api token
@@ -1198,16 +1198,16 @@ const userSettings = async ({ req, res, pwform, user }) => {
1198
1198
  ),
1199
1199
  // button for remove api token
1200
1200
  user.api_token &&
1201
- div(
1202
- { class: "mt-4 ms-2 d-inline-block" },
1203
- post_btn(
1204
- `/auth/remove-api-token`,
1205
- // TBD localization
1206
- user.api_token ? req.__("Remove") : req.__("Generate"),
1207
- req.csrfToken(),
1208
- { req: req, confirm: true }
1209
- )
1210
- ),
1201
+ div(
1202
+ { class: "mt-4 ms-2 d-inline-block" },
1203
+ post_btn(
1204
+ `/auth/remove-api-token`,
1205
+ // TBD localization
1206
+ user.api_token ? req.__("Remove") : req.__("Generate"),
1207
+ req.csrfToken(),
1208
+ { req: req, confirm: true }
1209
+ )
1210
+ ),
1211
1211
  ],
1212
1212
  };
1213
1213
  return {
@@ -1218,13 +1218,13 @@ const userSettings = async ({ req, res, pwform, user }) => {
1218
1218
  },
1219
1219
  ...(usersets
1220
1220
  ? [
1221
- {
1222
- type: "card",
1223
- class: "mt-0",
1224
- title: userSetsName,
1225
- contents: usersets,
1226
- },
1227
- ]
1221
+ {
1222
+ type: "card",
1223
+ class: "mt-0",
1224
+ title: userSetsName,
1225
+ contents: usersets,
1226
+ },
1227
+ ]
1228
1228
  : []),
1229
1229
  {
1230
1230
  type: "card",
@@ -1247,35 +1247,35 @@ const userSettings = async ({ req, res, pwform, user }) => {
1247
1247
  },
1248
1248
  ...(show2FAPolicy
1249
1249
  ? [
1250
- {
1251
- type: "card",
1252
- title: req.__("Two-factor authentication"),
1253
- contents: [
1254
- div(
1255
- user._attributes.totp_enabled
1256
- ? req.__("Two-factor authentication is enabled")
1257
- : req.__("Two-factor authentication is disabled")
1258
- ),
1259
- div(
1260
- user._attributes.totp_enabled
1261
- ? a(
1262
- {
1263
- href: "/auth/twofa/disable/totp",
1264
- class: "btn btn-danger mt-2",
1265
- },
1266
- req.__("Disable TWA")
1267
- )
1268
- : a(
1269
- {
1270
- href: "/auth/twofa/setup/totp",
1271
- class: "btn btn-primary mt-2",
1272
- },
1273
- req.__("Enable TWA")
1274
- )
1275
- ),
1276
- ],
1277
- },
1278
- ]
1250
+ {
1251
+ type: "card",
1252
+ title: req.__("Two-factor authentication"),
1253
+ contents: [
1254
+ div(
1255
+ user._attributes.totp_enabled
1256
+ ? req.__("Two-factor authentication is enabled")
1257
+ : req.__("Two-factor authentication is disabled")
1258
+ ),
1259
+ div(
1260
+ user._attributes.totp_enabled
1261
+ ? a(
1262
+ {
1263
+ href: "/auth/twofa/disable/totp",
1264
+ class: "btn btn-danger mt-2",
1265
+ },
1266
+ req.__("Disable TWA")
1267
+ )
1268
+ : a(
1269
+ {
1270
+ href: "/auth/twofa/setup/totp",
1271
+ class: "btn btn-primary mt-2",
1272
+ },
1273
+ req.__("Enable TWA")
1274
+ )
1275
+ ),
1276
+ ],
1277
+ },
1278
+ ]
1279
1279
  : []),
1280
1280
  ...(apikeycard ? [apikeycard] : []),
1281
1281
  ],
@@ -1328,18 +1328,12 @@ router.post(
1328
1328
  "/setlanguage",
1329
1329
  loggedIn,
1330
1330
  error_catcher(async (req, res) => {
1331
- const u = await User.findOne({ id: req.user.id });
1331
+ const u = await User.findForSession({ id: req.user.id });
1332
1332
  const newlang = available_languages[req.body.locale];
1333
1333
  if (newlang && u) {
1334
1334
  await u.set_language(req.body.locale);
1335
1335
  req.login(
1336
- {
1337
- email: u.email,
1338
- id: u.id,
1339
- role_id: u.role_id,
1340
- language: req.body.locale,
1341
- tenant: db.getTenantSchema(),
1342
- },
1336
+ u.session_object,
1343
1337
  function (err) {
1344
1338
  if (!err) {
1345
1339
  req.flash("success", req.__("Language changed to %s", newlang));
@@ -1443,16 +1437,11 @@ router.post(
1443
1437
  return;
1444
1438
  }
1445
1439
 
1446
- const u = await User.findOne({ id: req.user.id });
1440
+ const u = await User.findForSession({ id: req.user.id });
1447
1441
  await u.update({ email: form.values.email });
1448
1442
  u.email = form.values.email;
1449
1443
  req.login(
1450
- {
1451
- email: u.email,
1452
- id: u.id,
1453
- role_id: u.role_id,
1454
- tenant: db.getTenantSchema(),
1455
- },
1444
+ u.session_object,
1456
1445
  function (err) {
1457
1446
  if (!err) {
1458
1447
  Trigger.emitEvent("Login", null, u);
@@ -1543,7 +1532,7 @@ router.all(
1543
1532
  return;
1544
1533
  }
1545
1534
  if (wfres.verified === true) {
1546
- const user = await User.findOne({ id: req.user.id });
1535
+ const user = await User.findForSession({ id: req.user.id });
1547
1536
  await user.set_to_verified();
1548
1537
  req.flash("success", req.__("User verified"));
1549
1538
  user.relogin(req);
@@ -1580,9 +1569,8 @@ router.get(
1580
1569
  // generate QR code for scanning into Google Authenticator
1581
1570
  // reference: https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
1582
1571
  const site_name = getState().getConfig("site_name");
1583
- const otpUrl = `otpauth://totp/${
1584
- user.email
1585
- }?secret=${encodedKey}&period=30&issuer=${encodeURIComponent(site_name)}`;
1572
+ const otpUrl = `otpauth://totp/${user.email
1573
+ }?secret=${encodedKey}&period=30&issuer=${encodeURIComponent(site_name)}`;
1586
1574
  const image = await qrcode.toDataURL(otpUrl);
1587
1575
  res.sendWrap(req.__("Setup two-factor authentication"), {
1588
1576
  type: "card",
@@ -1753,7 +1741,7 @@ router.post(
1753
1741
  failureFlash: true,
1754
1742
  }),
1755
1743
  error_catcher(async (req, res) => {
1756
- const user = await User.findOne({ id: req.user.pending_user.id });
1744
+ const user = await User.findForSession({ id: req.user.pending_user.id });
1757
1745
  user.relogin(req);
1758
1746
  Trigger.emitEvent("Login", null, user);
1759
1747
  res.redirect("/");
package/errors.js CHANGED
@@ -7,55 +7,58 @@ const { pre, p, text, h3 } = require("@saltcorn/markup/tags");
7
7
  const Crash = require("@saltcorn/data/models/crash");
8
8
  const { getState } = require("@saltcorn/data/db/state");
9
9
 
10
- module.exports =
11
- /**
12
- *
13
- * @param {object} err
14
- * @param {object} req
15
- * @param {object} res
16
- * @param {*} next
17
- * @returns {Promise<void>}
18
- */
19
- async function (err, req, res, next) {
20
- if (!req.__) req.__ = (s) => s;
10
+ module.exports =
11
+ /**
12
+ *
13
+ * @param {object} err
14
+ * @param {object} req
15
+ * @param {object} res
16
+ * @param {*} next
17
+ * @returns {Promise<void>}
18
+ */
19
+ async function (err, req, res, next) {
20
+ if (!req.__) req.__ = (s) => s;
21
21
 
22
- const devmode = getState().getConfig("development_mode", false);
23
- const log_sql = getState().getConfig("log_sql", false);
24
- const role = (req.user || {}).role_id || 10;
25
- if (err.message && err.message.includes("invalid csrf token")) {
26
- console.error(err.message);
22
+ const devmode = getState().getConfig("development_mode", false);
23
+ const log_sql = getState().getConfig("log_sql", false);
24
+ const role = (req.user || {}).role_id || 10;
25
+ if (err.message && err.message.includes("invalid csrf token")) {
26
+ console.error(err.message);
27
27
 
28
- req.flash("error", req.__("Invalid form data, try again"));
29
- if (req.url && req.url.includes("/auth/login")) res.redirect("/auth/login");
30
- else res.redirect("/");
31
- return;
32
- }
33
- const code = err.httpCode || 500;
34
- const headline = err.headline || "An error occurred";
35
- const severity = err.severity || 2;
36
- const createCrash = severity <= 3;
37
- console.error(err.stack);
38
- if (!(devmode && log_sql) && createCrash) await Crash.create(err, req);
28
+ req.flash("error", req.__("Invalid form data, try again"));
29
+ if (req.url && req.url.includes("/auth/login"))
30
+ res.redirect("/auth/login");
31
+ else res.redirect("/");
32
+ return;
33
+ }
34
+ const code = err.httpCode || 500;
35
+ const headline = err.headline || "An error occurred";
36
+ const severity = err.severity || 2;
37
+ const createCrash = severity <= 3;
38
+ //console.error(err.stack);
39
+ if (!(devmode && log_sql) && createCrash) await Crash.create(err, req);
39
40
 
40
- if (req.xhr) {
41
- res
42
- .status(code)
43
- .send(
44
- devmode || role === 1 ? text(err.message) : req.__("An error occurred")
45
- );
46
- } else
47
- res
48
- .status(code)
49
- .sendWrap(
50
- req.__(headline),
51
- devmode ? pre(text(err.stack)) : h3(req.__(headline)),
52
- role === 1 && !devmode ? pre(text(err.message)) : "",
53
- createCrash
54
- ? p(
55
- req.__(
56
- `A report has been logged and a team of bug-squashing squirrels has been dispatched to deal with the situation.`
41
+ if (req.xhr) {
42
+ res
43
+ .status(code)
44
+ .send(
45
+ devmode || role === 1
46
+ ? text(err.message)
47
+ : req.__("An error occurred")
48
+ );
49
+ } else
50
+ res
51
+ .status(code)
52
+ .sendWrap(
53
+ req.__(headline),
54
+ devmode ? pre(text(err.stack)) : h3(req.__(headline)),
55
+ role === 1 && !devmode ? pre(text(err.message)) : "",
56
+ createCrash
57
+ ? p(
58
+ req.__(
59
+ `A report has been logged and a team of bug-squashing squirrels has been dispatched to deal with the situation.`
60
+ )
57
61
  )
58
- )
59
- : ""
60
- );
61
- };
62
+ : ""
63
+ );
64
+ };
package/locales/en.json CHANGED
@@ -921,5 +921,50 @@
921
921
  "Restoring automated backup": "Restoring automated backup",
922
922
  "No errors detected during configuration check": "No errors detected during configuration check",
923
923
  "%s view - %s on %s": "%s view - %s on %s",
924
- "Back": "Back"
925
- }
924
+ "Please select at least one platform (android or iOS).": "Please select at least one platform (android or iOS).",
925
+ "Back": "Back",
926
+ "Periodic snapshots enabled": "Periodic snapshots enabled",
927
+ "Snapshot will be made every hour if there are changes": "Snapshot will be made every hour if there are changes",
928
+ "Snapshots": "Snapshots",
929
+ "Snapshot settings updated": "Snapshot settings updated",
930
+ "Download snapshots": "Download snapshots",
931
+ "Snapshot successful": "Snapshot successful",
932
+ "System logging verbosity": "System logging verbosity",
933
+ "Destination URL Formula": "Destination URL Formula",
934
+ "Pattern": "Pattern",
935
+ "View pattern": "View pattern",
936
+ "The view pattern sets the foundation of how the view relates to the table and the behaviour of the view": "The view pattern sets the foundation of how the view relates to the table and the behaviour of the view",
937
+ "Views display data from tables. A view is a view pattern applied to a table, with configuration.": "Views display data from tables. A view is a view pattern applied to a table, with configuration.",
938
+ "Modules": "Modules",
939
+ "Module installation and control": "Module installation and control",
940
+ "Module store": "Module store",
941
+ "Module": "Module",
942
+ "View patterns": "View patterns",
943
+ "%s module information": "%s module information",
944
+ "Upgrade installed modules": "Upgrade installed modules",
945
+ "Add another module": "Add another module",
946
+ "Add module": "Add module",
947
+ "Module name": "Module name",
948
+ "Source of module for install. Few options:npm - download from npm repository,local - get from local file system,github - download from github,git - get from git": "Source of module for install. Few options:npm - download from npm repository,local - get from local file system,github - download from github,git - get from git",
949
+ "Version of module, latest is default value": "Version of module, latest is default value",
950
+ "For npm - name of npm package, e.g. @saltcorn/html or saltcorn-gantt, check at npmjs.com, for local - absolute path to module folder in file system, e.g. C:\\gitsrc\\any-bootstrap-theme\\, for github - name of github project.": "For npm - name of npm package, e.g. @saltcorn/html or saltcorn-gantt, check at npmjs.com, for local - absolute path to module folder in file system, e.g. C:\\gitsrc\\any-bootstrap-theme\\, for github - name of github project.",
951
+ "Modules up-to-date": "Modules up-to-date",
952
+ "User must have this role or higher to read rows from the table, unless they are the owner": "User must have this role or higher to read rows from the table, unless they are the owner",
953
+ "User must have this role or higher to edit or create new rows in the table, unless they are the owner": "User must have this role or higher to edit or create new rows in the table, unless they are the owner",
954
+ "Tag": "Tag",
955
+ "Tagname": "Tagname",
956
+ "Create tag": "Create tag",
957
+ "Tags": "Tags",
958
+ "New tag": "New tag",
959
+ "Tag name": "Tag name",
960
+ "%s Tag": "%s Tag",
961
+ "Remove From Tag": "Remove From Tag",
962
+ "Add tables": "Add tables",
963
+ "Add views": "Add views",
964
+ "Add tages": "Add tages",
965
+ "Trigger": "Trigger",
966
+ "Add %s to tag": "Add %s to tag",
967
+ "Tag %s deleted": "Tag %s deleted",
968
+ "Tag %s created": "Tag %s created"
969
+
970
+ }
package/locales/it.json CHANGED
@@ -478,5 +478,6 @@
478
478
  "Generate": "Generate",
479
479
  "Two-factor authentication": "Two-factor authentication",
480
480
  "Two-factor authentication is disabled": "Two-factor authentication is disabled",
481
- "Enable TWA": "Enable TWA"
481
+ "Enable TWA": "Enable TWA",
482
+ "Modules": "Modules"
482
483
  }
package/locales/ru.json CHANGED
@@ -26,7 +26,7 @@
26
26
  "ID": "ID",
27
27
  "Email": "Email",
28
28
  "Role": "Роль",
29
- "View": "Посмотреть",
29
+ "View": "Представление",
30
30
  "Delete": "Удалить",
31
31
  "Add user": "Создать пользователя",
32
32
  "New user": "Новый пользователь",
@@ -783,15 +783,51 @@
783
783
  "Disable TWA": "Отключить TWA",
784
784
  "Enable TWA": "Включить TWA",
785
785
  "Deleted all rows": "Удалены все строки",
786
- "Use this link: <a href=\"%s\">%s</a> to revisit your application at any time.": "В дальнешем используйте ссылку: <a href=\"%s\">%s</a> для входа в созданное приложение.",
786
+ "Use this link: <a href=\"%s\">%s</a> to revisit your application at any time.": "В дальнейшем используйте ссылку: <a href=\"%s\">%s</a> для входа в созданное приложение.",
787
787
  "To login to a previously created application, go to: ": "Чтобы авторизоваться в ранее созданном приложении, перейдите по ссылке: ",
788
788
  "Has channels?": "Связано с каналом?",
789
789
  "Menu, search, languages and tenants": "Меню, поиск, языки и подсайты(тенанты)",
790
790
  "Images and other files for download": "Изображения и другие файлы для загрузки",
791
791
  "Channels to create events for. Separate by comma; leave blank for all": "Каналы, для которых создаются собятия. Разделяются ; Не заполняйте, чтобы использовать события для всех каналов",
792
792
  "Event Name": "Имя события",
793
- "Plugins (Extensions) Store endpoint": "Plugins (Extensions) Store endpoint",
794
- "Packs Store endpoint": "Packs Store endpoint",
795
- "The endpoint of plugins store.": "The endpoint of plugins store.",
796
- "The endpoint of packs store.": "The endpoint of packs store."
793
+ "Plugins (Extensions) Store endpoint": "Адрес (endpoint) магазина плагинов",
794
+ "Packs Store endpoint": "Адрес (endpoint) магазина паков",
795
+ "The endpoint of plugins store.": "Адрес (endpoint) магазина плагинов.",
796
+ "The endpoint of packs store.": "Адрес (endpoint) магазина паков.",
797
+ "Modules": "Модули",
798
+ "Frequency": "Регулярность",
799
+ "Destination": "Местоположение",
800
+ "Directory": "Папка",
801
+ "Expiration in days": "Срок хранения (в днях)",
802
+ "Delete old backup files in this directory after the set number of days": "Старые резервные копии в этой папке будут удалять после истечения срока хранения ",
803
+ "Backup settings updated": "Настройки резервного копирования обновлены",
804
+ "Periodic snapshots enabled": "Регулярные снимки (снэпшоты) активированы",
805
+ "Snapshot will be made every hour if there are changes": "Снимки (снэпшоты) будут выполняться каждый час",
806
+ "Manual backup": "Ручное резервное копирование",
807
+ "Automated backup": "Автоматическое резервное копирование",
808
+ "Snapshots": "Снимки (снэпшоты)",
809
+ "Mobile app": "Мобильное приложение",
810
+ "Module store": "Магазин модулей",
811
+ "Upgrade installed modules": "Обновить установленные модули",
812
+ "Add another module": "Добавить модуль",
813
+ "Module": "Модуль",
814
+ "Module installation and control": "Установка и управление модулями",
815
+ "System logging verbosity": "Уровень логирования системы",
816
+ "Source of module for install. Few options:npm - download from npm repository,local - get from local file system,github - download from github,git - get from git": "Источник для установки модуля:npm - загрузка из репозитория npm,local - установка из локальной файловой системы,github - загрузка из github,git - загрузка из git",
817
+ "Version of module, latest is default value": "Версия модуля, значение по-умолчанию latest",
818
+ "For npm - name of npm package, e.g. @saltcorn/html or saltcorn-gantt, check at npmjs.com, for local - absolute path to module folder in file system, e.g. C:\\gitsrc\\any-bootstrap-theme\\, for github - name of github project.": "для npm - имя npm пактеа, например, @saltcorn/html или saltcorn-gantt, см. npmjs.com, для local - абсолютный путь к папке модуля в локальной файловой системе, например, C:\\gitsrc\\any-bootstrap-theme\\, для github - имя проекта github.",
819
+ "Modules up-to-date": "Модули обновлены до актуальной версии",
820
+ "Add module": "Добавить модуль",
821
+ "Module name": "Имя модуля",
822
+ "Build mobile app": "Создать мобильное приложение",
823
+ "Please select at least one platform (android or iOS).": "Пожалуйста выберите как минимум одну платформу (android или iOS).",
824
+ "Please enter a valid server URL.": "Пожалуйста введите корректный URL сервера.",
825
+ "Only the android build supports docker.": "Только сборка для android поддерживает docker.",
826
+ "Entry point": "Точка входа в приложение",
827
+ "Platform": "Платформа",
828
+ "docker": "docker",
829
+ "App file": "Файл приложения",
830
+ "Server URL": "URL сервера",
831
+ "android": "android",
832
+ "iOS": "iOS"
797
833
  }
package/locales/zh.json CHANGED
@@ -179,7 +179,7 @@
179
179
  "Pack %s installed": "已安装包 %s",
180
180
  "Pack %s uninstalled": "包 %s 已卸载",
181
181
  "Uninstall": "卸载",
182
- "Result preview for ": "结果预览", // todo
182
+ "Result preview for ": "结果预览",
183
183
  "Choose views for <a href=\"/search\">search results</a> for each table.<br/>Set to blank to omit table from global search.": "为每个表选择<a href=\"/search\">搜索结果</a>的视图。<br/>为空则可从全局搜索中忽略表。",
184
184
  "These tables lack suitable views: ": "这些表缺少合适的视图:",
185
185
  "Search configuration": "搜索配置",
package/markup/admin.js CHANGED
@@ -245,6 +245,13 @@ const send_files_page = (args) => {
245
245
  });
246
246
  };
247
247
 
248
+ const send_tags_page = (args) => {
249
+ return send_settings_page({
250
+ main_section: "Tags",
251
+ ...args,
252
+ });
253
+ };
254
+
248
255
  /**
249
256
  * Send Events Page
250
257
  * @param {object} args
@@ -281,6 +288,7 @@ const send_admin_page = (args) => {
281
288
  { text: "Backup", href: "/admin/backup" },
282
289
  { text: "Email", href: "/admin/email" },
283
290
  { text: "System", href: "/admin/system" },
291
+ ...(isRoot ? [{ text: "Tag", href: "/tag" }] : []),
284
292
  ...(isRoot
285
293
  ? [{ text: "Mobile app", href: "/admin/build-mobile-app" }]
286
294
  : []),
@@ -345,7 +353,12 @@ const flash_restart = (req) => {
345
353
  * @param {*} opts.formArgs
346
354
  * @returns {Promise<Form>}
347
355
  */
348
- const config_fields_form = async ({ field_names, req, action, ...formArgs }) => {
356
+ const config_fields_form = async ({
357
+ field_names,
358
+ req,
359
+ action,
360
+ ...formArgs
361
+ }) => {
349
362
  const values = {};
350
363
  const state = getState();
351
364
  const fields = [];
@@ -512,4 +525,5 @@ module.exports = {
512
525
  save_config_from_form,
513
526
  flash_restart_if_required,
514
527
  flash_restart,
528
+ send_tags_page,
515
529
  };
@@ -87,7 +87,7 @@ const plugin_functions_info_card = (plugin, req) => ({
87
87
  */
88
88
  const plugin_viewtemplates_info_card = (plugin, req) => ({
89
89
  type: "card",
90
- title: req.__("View templates"),
90
+ title: req.__("View patterns"),
91
91
  contents: withCfg(plugin, "viewtemplates", [])
92
92
  .map(({ name, description }) => div(h4(name), p(description)))
93
93
  .join("<hr>"),
@@ -101,10 +101,10 @@ const showRepository = (repo) =>
101
101
  !repo
102
102
  ? repo
103
103
  : repo.url
104
- ? link(repo.url, repo.url)
105
- : repo.startsWith && repo.startsWith("github:")
106
- ? link(repo.replace("github:", "https://github.com/"), repo)
107
- : repo;
104
+ ? link(repo.url, repo.url)
105
+ : repo.startsWith && repo.startsWith("github:")
106
+ ? link(repo.replace("github:", "https://github.com/"), repo)
107
+ : repo;
108
108
 
109
109
  module.exports = {
110
110
  plugin_types_info_card,