@nocobase/plugin-multi-portal 2.2.0-alpha.10
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/LICENSE.txt +107 -0
- package/README.md +14 -0
- package/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/client.d.ts +2 -0
- package/client.js +1 -0
- package/dist/client/index.d.ts +9 -0
- package/dist/client/index.js +10 -0
- package/dist/client/locale.d.ts +10 -0
- package/dist/client/models/index.d.ts +11 -0
- package/dist/client/plugin.d.ts +13 -0
- package/dist/client-v2/354.ead3e96143164cd5.js +10 -0
- package/dist/client-v2/499.ef2ff0c022687485.js +10 -0
- package/dist/client-v2/778.23ba409a924ee34a.js +10 -0
- package/dist/client-v2/845.f2b0ce44ae4be707.js +10 -0
- package/dist/client-v2/entryActions/PortalEntryActionModel.d.ts +52 -0
- package/dist/client-v2/entryActions/portalEntryActionStore.d.ts +36 -0
- package/dist/client-v2/entryActions/registerPortalEntryActions.d.ts +10 -0
- package/dist/client-v2/entryActions/types.d.ts +41 -0
- package/dist/client-v2/entryActions/url.d.ts +10 -0
- package/dist/client-v2/index.d.ts +9 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/client-v2/layoutRegistration.d.ts +38 -0
- package/dist/client-v2/locale.d.ts +13 -0
- package/dist/client-v2/models/MultiPortalBlockModel.d.ts +13 -0
- package/dist/client-v2/models/MultiPortalMobilePageModels.d.ts +34 -0
- package/dist/client-v2/pages/MultiPortalsPage.d.ts +80 -0
- package/dist/client-v2/permissions/MultiPortalPermissionsTab.d.ts +21 -0
- package/dist/client-v2/permissions/multiPortalPermissions.d.ts +12 -0
- package/dist/client-v2/plugin.d.ts +13 -0
- package/dist/client-v2/routeRepositoryScope.d.ts +11 -0
- package/dist/client-v2/routeUrl.d.ts +18 -0
- package/dist/externalVersion.js +25 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +48 -0
- package/dist/locale/en-US.json +82 -0
- package/dist/locale/zh-CN.json +82 -0
- package/dist/node_modules/tar/dist/commonjs/create.d.ts +3 -0
- package/dist/node_modules/tar/dist/commonjs/create.js +82 -0
- package/dist/node_modules/tar/dist/commonjs/cwd-error.d.ts +8 -0
- package/dist/node_modules/tar/dist/commonjs/cwd-error.js +18 -0
- package/dist/node_modules/tar/dist/commonjs/extract.d.ts +3 -0
- package/dist/node_modules/tar/dist/commonjs/extract.js +88 -0
- package/dist/node_modules/tar/dist/commonjs/get-write-flag.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/get-write-flag.js +32 -0
- package/dist/node_modules/tar/dist/commonjs/header.d.ts +53 -0
- package/dist/node_modules/tar/dist/commonjs/header.js +312 -0
- package/dist/node_modules/tar/dist/commonjs/index.d.ts +20 -0
- package/dist/node_modules/tar/dist/commonjs/index.js +64 -0
- package/dist/node_modules/tar/dist/commonjs/index.min.js +1 -0
- package/dist/node_modules/tar/dist/commonjs/large-numbers.d.ts +3 -0
- package/dist/node_modules/tar/dist/commonjs/large-numbers.js +99 -0
- package/dist/node_modules/tar/dist/commonjs/list.d.ts +7 -0
- package/dist/node_modules/tar/dist/commonjs/list.js +145 -0
- package/dist/node_modules/tar/dist/commonjs/make-command.d.ts +49 -0
- package/dist/node_modules/tar/dist/commonjs/make-command.js +52 -0
- package/dist/node_modules/tar/dist/commonjs/mkdir.d.ts +25 -0
- package/dist/node_modules/tar/dist/commonjs/mkdir.js +187 -0
- package/dist/node_modules/tar/dist/commonjs/mode-fix.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/mode-fix.js +29 -0
- package/dist/node_modules/tar/dist/commonjs/normalize-unicode.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/normalize-unicode.js +38 -0
- package/dist/node_modules/tar/dist/commonjs/normalize-windows-path.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/normalize-windows-path.js +12 -0
- package/dist/node_modules/tar/dist/commonjs/options.d.ts +596 -0
- package/dist/node_modules/tar/dist/commonjs/options.js +66 -0
- package/dist/node_modules/tar/dist/commonjs/pack.d.ts +105 -0
- package/dist/node_modules/tar/dist/commonjs/pack.js +545 -0
- package/dist/node_modules/tar/dist/commonjs/package.json +3 -0
- package/dist/node_modules/tar/dist/commonjs/parse.d.ts +85 -0
- package/dist/node_modules/tar/dist/commonjs/parse.js +610 -0
- package/dist/node_modules/tar/dist/commonjs/path-reservations.d.ts +11 -0
- package/dist/node_modules/tar/dist/commonjs/path-reservations.js +170 -0
- package/dist/node_modules/tar/dist/commonjs/pax.d.ts +26 -0
- package/dist/node_modules/tar/dist/commonjs/pax.js +158 -0
- package/dist/node_modules/tar/dist/commonjs/process-umask.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/process-umask.js +7 -0
- package/dist/node_modules/tar/dist/commonjs/read-entry.d.ts +36 -0
- package/dist/node_modules/tar/dist/commonjs/read-entry.js +136 -0
- package/dist/node_modules/tar/dist/commonjs/replace.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/replace.js +227 -0
- package/dist/node_modules/tar/dist/commonjs/strip-absolute-path.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/strip-absolute-path.js +29 -0
- package/dist/node_modules/tar/dist/commonjs/strip-trailing-slashes.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/strip-trailing-slashes.js +18 -0
- package/dist/node_modules/tar/dist/commonjs/symlink-error.d.ts +9 -0
- package/dist/node_modules/tar/dist/commonjs/symlink-error.js +19 -0
- package/dist/node_modules/tar/dist/commonjs/types.d.ts +7 -0
- package/dist/node_modules/tar/dist/commonjs/types.js +50 -0
- package/dist/node_modules/tar/dist/commonjs/unpack.d.ts +101 -0
- package/dist/node_modules/tar/dist/commonjs/unpack.js +932 -0
- package/dist/node_modules/tar/dist/commonjs/update.d.ts +2 -0
- package/dist/node_modules/tar/dist/commonjs/update.js +33 -0
- package/dist/node_modules/tar/dist/commonjs/warn-method.d.ts +24 -0
- package/dist/node_modules/tar/dist/commonjs/warn-method.js +31 -0
- package/dist/node_modules/tar/dist/commonjs/winchars.d.ts +3 -0
- package/dist/node_modules/tar/dist/commonjs/winchars.js +14 -0
- package/dist/node_modules/tar/dist/commonjs/write-entry.d.ts +129 -0
- package/dist/node_modules/tar/dist/commonjs/write-entry.js +698 -0
- package/dist/node_modules/tar/dist/esm/create.d.ts +3 -0
- package/dist/node_modules/tar/dist/esm/create.js +76 -0
- package/dist/node_modules/tar/dist/esm/cwd-error.d.ts +8 -0
- package/dist/node_modules/tar/dist/esm/cwd-error.js +14 -0
- package/dist/node_modules/tar/dist/esm/extract.d.ts +3 -0
- package/dist/node_modules/tar/dist/esm/extract.js +49 -0
- package/dist/node_modules/tar/dist/esm/get-write-flag.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/get-write-flag.js +26 -0
- package/dist/node_modules/tar/dist/esm/header.d.ts +53 -0
- package/dist/node_modules/tar/dist/esm/header.js +275 -0
- package/dist/node_modules/tar/dist/esm/index.d.ts +20 -0
- package/dist/node_modules/tar/dist/esm/index.js +20 -0
- package/dist/node_modules/tar/dist/esm/index.min.js +4 -0
- package/dist/node_modules/tar/dist/esm/large-numbers.d.ts +3 -0
- package/dist/node_modules/tar/dist/esm/large-numbers.js +94 -0
- package/dist/node_modules/tar/dist/esm/list.d.ts +7 -0
- package/dist/node_modules/tar/dist/esm/list.js +105 -0
- package/dist/node_modules/tar/dist/esm/make-command.d.ts +49 -0
- package/dist/node_modules/tar/dist/esm/make-command.js +48 -0
- package/dist/node_modules/tar/dist/esm/mkdir.d.ts +25 -0
- package/dist/node_modules/tar/dist/esm/mkdir.js +179 -0
- package/dist/node_modules/tar/dist/esm/mode-fix.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/mode-fix.js +25 -0
- package/dist/node_modules/tar/dist/esm/normalize-unicode.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/normalize-unicode.js +34 -0
- package/dist/node_modules/tar/dist/esm/normalize-windows-path.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/normalize-windows-path.js +9 -0
- package/dist/node_modules/tar/dist/esm/options.d.ts +596 -0
- package/dist/node_modules/tar/dist/esm/options.js +54 -0
- package/dist/node_modules/tar/dist/esm/pack.d.ts +105 -0
- package/dist/node_modules/tar/dist/esm/pack.js +503 -0
- package/dist/node_modules/tar/dist/esm/package.json +3 -0
- package/dist/node_modules/tar/dist/esm/parse.d.ts +85 -0
- package/dist/node_modules/tar/dist/esm/parse.js +606 -0
- package/dist/node_modules/tar/dist/esm/path-reservations.d.ts +11 -0
- package/dist/node_modules/tar/dist/esm/path-reservations.js +166 -0
- package/dist/node_modules/tar/dist/esm/pax.d.ts +26 -0
- package/dist/node_modules/tar/dist/esm/pax.js +154 -0
- package/dist/node_modules/tar/dist/esm/process-umask.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/process-umask.js +3 -0
- package/dist/node_modules/tar/dist/esm/read-entry.d.ts +36 -0
- package/dist/node_modules/tar/dist/esm/read-entry.js +132 -0
- package/dist/node_modules/tar/dist/esm/replace.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/replace.js +221 -0
- package/dist/node_modules/tar/dist/esm/strip-absolute-path.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/strip-absolute-path.js +25 -0
- package/dist/node_modules/tar/dist/esm/strip-trailing-slashes.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/strip-trailing-slashes.js +14 -0
- package/dist/node_modules/tar/dist/esm/symlink-error.d.ts +9 -0
- package/dist/node_modules/tar/dist/esm/symlink-error.js +15 -0
- package/dist/node_modules/tar/dist/esm/types.d.ts +7 -0
- package/dist/node_modules/tar/dist/esm/types.js +45 -0
- package/dist/node_modules/tar/dist/esm/unpack.d.ts +101 -0
- package/dist/node_modules/tar/dist/esm/unpack.js +891 -0
- package/dist/node_modules/tar/dist/esm/update.d.ts +2 -0
- package/dist/node_modules/tar/dist/esm/update.js +30 -0
- package/dist/node_modules/tar/dist/esm/warn-method.d.ts +24 -0
- package/dist/node_modules/tar/dist/esm/warn-method.js +27 -0
- package/dist/node_modules/tar/dist/esm/winchars.d.ts +3 -0
- package/dist/node_modules/tar/dist/esm/winchars.js +9 -0
- package/dist/node_modules/tar/dist/esm/write-entry.d.ts +129 -0
- package/dist/node_modules/tar/dist/esm/write-entry.js +656 -0
- package/dist/node_modules/tar/node_modules/chownr/dist/commonjs/index.d.ts +3 -0
- package/dist/node_modules/tar/node_modules/chownr/dist/commonjs/index.js +93 -0
- package/dist/node_modules/tar/node_modules/chownr/dist/commonjs/package.json +3 -0
- package/dist/node_modules/tar/node_modules/chownr/dist/esm/index.d.ts +3 -0
- package/dist/node_modules/tar/node_modules/chownr/dist/esm/index.js +85 -0
- package/dist/node_modules/tar/node_modules/chownr/dist/esm/package.json +3 -0
- package/dist/node_modules/tar/node_modules/chownr/package.json +69 -0
- package/dist/node_modules/tar/node_modules/minipass/dist/commonjs/index.d.ts +549 -0
- package/dist/node_modules/tar/node_modules/minipass/dist/commonjs/index.js +1028 -0
- package/dist/node_modules/tar/node_modules/minipass/dist/commonjs/package.json +3 -0
- package/dist/node_modules/tar/node_modules/minipass/dist/esm/index.d.ts +549 -0
- package/dist/node_modules/tar/node_modules/minipass/dist/esm/index.js +1018 -0
- package/dist/node_modules/tar/node_modules/minipass/dist/esm/package.json +3 -0
- package/dist/node_modules/tar/node_modules/minipass/package.json +82 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/commonjs/constants.d.ts +2 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/commonjs/constants.js +123 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/commonjs/index.d.ts +99 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/commonjs/index.js +416 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/commonjs/package.json +3 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/esm/constants.d.ts +2 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/esm/constants.js +117 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/esm/index.d.ts +99 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/esm/index.js +363 -0
- package/dist/node_modules/tar/node_modules/minizlib/dist/esm/package.json +3 -0
- package/dist/node_modules/tar/node_modules/minizlib/package.json +80 -0
- package/dist/node_modules/tar/node_modules/yallist/dist/commonjs/index.d.ts +39 -0
- package/dist/node_modules/tar/node_modules/yallist/dist/commonjs/index.js +384 -0
- package/dist/node_modules/tar/node_modules/yallist/dist/commonjs/package.json +3 -0
- package/dist/node_modules/tar/node_modules/yallist/dist/esm/index.d.ts +39 -0
- package/dist/node_modules/tar/node_modules/yallist/dist/esm/index.js +379 -0
- package/dist/node_modules/tar/node_modules/yallist/dist/esm/package.json +3 -0
- package/dist/node_modules/tar/node_modules/yallist/package.json +68 -0
- package/dist/node_modules/tar/package.json +1 -0
- package/dist/server/collections/desktopRoutes.d.ts +14 -0
- package/dist/server/collections/desktopRoutes.js +48 -0
- package/dist/server/collections/extendRoleField.d.ts +14 -0
- package/dist/server/collections/extendRoleField.js +51 -0
- package/dist/server/collections/multiPortals.d.ts +10 -0
- package/dist/server/collections/multiPortals.js +128 -0
- package/dist/server/collections/rolesMultiPortalDesktopRoutes.d.ts +10 -0
- package/dist/server/collections/rolesMultiPortalDesktopRoutes.js +76 -0
- package/dist/server/collections/rolesMultiPortalRoutePolicies.d.ts +10 -0
- package/dist/server/collections/rolesMultiPortalRoutePolicies.js +73 -0
- package/dist/server/collections/rolesMultiPortals.d.ts +10 -0
- package/dist/server/collections/rolesMultiPortals.js +68 -0
- package/dist/server/ensureDefaultRoleMultiPortalAccess.d.ts +11 -0
- package/dist/server/ensureDefaultRoleMultiPortalAccess.js +70 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.js +42 -0
- package/dist/server/migrations/20260723105200-drop-ui-layout-foreign-key-constraint.d.ts +13 -0
- package/dist/server/migrations/20260723105200-drop-ui-layout-foreign-key-constraint.js +104 -0
- package/dist/server/plugin.d.ts +63 -0
- package/dist/server/plugin.js +2130 -0
- package/package.json +34 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
|
@@ -0,0 +1,2130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
var plugin_exports = {};
|
|
38
|
+
__export(plugin_exports, {
|
|
39
|
+
PluginMultiPortalServer: () => PluginMultiPortalServer,
|
|
40
|
+
default: () => plugin_default
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(plugin_exports);
|
|
43
|
+
var import_server = require("@nocobase/server");
|
|
44
|
+
var import_utils = require("@nocobase/utils");
|
|
45
|
+
var import_child_process = require("child_process");
|
|
46
|
+
var import_fs = __toESM(require("fs"));
|
|
47
|
+
var import_os = __toESM(require("os"));
|
|
48
|
+
var import_path = __toESM(require("path"));
|
|
49
|
+
var import_url = require("url");
|
|
50
|
+
var import_promises = require("stream/promises");
|
|
51
|
+
var tar = __toESM(require("tar"));
|
|
52
|
+
var import_zlib = require("zlib");
|
|
53
|
+
var import_ensureDefaultRoleMultiPortalAccess = require("./ensureDefaultRoleMultiPortalAccess");
|
|
54
|
+
const MULTI_PORTAL_RUNTIME_FIELDS = [
|
|
55
|
+
"uid",
|
|
56
|
+
"title",
|
|
57
|
+
"portalType",
|
|
58
|
+
"portalName",
|
|
59
|
+
"routePath",
|
|
60
|
+
"authCheck",
|
|
61
|
+
"enabled"
|
|
62
|
+
];
|
|
63
|
+
const MULTI_PORTAL_RUNTIME_QUERY_FIELDS = [...MULTI_PORTAL_RUNTIME_FIELDS, "uiLayoutUid"];
|
|
64
|
+
const MULTI_PORTAL_ACCESSIBLE_FIELDS = [
|
|
65
|
+
"uid",
|
|
66
|
+
"title",
|
|
67
|
+
"icon",
|
|
68
|
+
"portalType",
|
|
69
|
+
"portalName",
|
|
70
|
+
"routePath",
|
|
71
|
+
"authCheck",
|
|
72
|
+
"enabled"
|
|
73
|
+
];
|
|
74
|
+
const MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS = [...MULTI_PORTAL_ACCESSIBLE_FIELDS, "uiLayoutUid"];
|
|
75
|
+
const MULTI_PORTAL_UI_LAYOUT_RUNTIME_FIELDS = ["layoutType"];
|
|
76
|
+
const DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS = ["id", "title", "hidden", "parentId", "options"];
|
|
77
|
+
const UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG = "plugin-ui-layout:desktop-route-write-layout";
|
|
78
|
+
const MAIN_APP_NAME = "main";
|
|
79
|
+
const UNION_ROLE_KEY = "__union__";
|
|
80
|
+
const MULTI_PORTAL_MANIFEST_NAMESPACE = "multi-portal";
|
|
81
|
+
const MULTI_PORTAL_MANIFEST_SYNC_MESSAGE_TYPE = "multi-portal:app-manifest-changed";
|
|
82
|
+
const DEFAULT_INIT_PORTAL_TYPE = "no-code";
|
|
83
|
+
const DEFAULT_INIT_PORTAL_NAME = "admin";
|
|
84
|
+
const DEFAULT_INIT_PORTAL_TEMPLATE = "@nocobase/portal-template-default";
|
|
85
|
+
const PORTAL_CLIENT_PREFIX = "x";
|
|
86
|
+
const PORTAL_DEPLOY_UPLOAD_LIMIT = 200 * 1024 * 1024;
|
|
87
|
+
const PORTAL_DEPLOY_UPLOAD_DIR_PREFIX = "nocobase-portal-dist-upload-";
|
|
88
|
+
const PORTAL_PUBLIC_DIR_MODE = 493;
|
|
89
|
+
const PORTAL_PUBLIC_FILE_MODE = 420;
|
|
90
|
+
const PORTAL_TEMPLATE_NPM_PACK_TIMEOUT_MS = 3e4;
|
|
91
|
+
const DEFAULT_MULTI_PORTAL_UID = "__default_portal__";
|
|
92
|
+
const MULTI_PORTAL_SLUG_PATTERN = /^[a-z0-9_-]+$/;
|
|
93
|
+
const INIT_PORTAL_TYPES = ["no-code", "ai"];
|
|
94
|
+
const DEFAULT_MULTI_PORTAL_UIDS = [DEFAULT_MULTI_PORTAL_UID];
|
|
95
|
+
const DEFAULT_MULTI_PORTAL_UID_SET = new Set(DEFAULT_MULTI_PORTAL_UIDS);
|
|
96
|
+
const MULTI_PORTAL_MANAGEMENT_ACTIONS = [
|
|
97
|
+
"multiPortals:list",
|
|
98
|
+
"multiPortals:get",
|
|
99
|
+
"multiPortals:getLog",
|
|
100
|
+
"multiPortals:create",
|
|
101
|
+
"multiPortals:update",
|
|
102
|
+
"multiPortals:firstOrCreate",
|
|
103
|
+
"multiPortals:destroy",
|
|
104
|
+
"multiPortals:deploy",
|
|
105
|
+
"multiPortals:pullSource",
|
|
106
|
+
"multiPortals:pushSource"
|
|
107
|
+
];
|
|
108
|
+
const ROLE_MULTI_PORTAL_PERMISSION_ACTIONS = [
|
|
109
|
+
"roles.multiPortals:*",
|
|
110
|
+
"rolesMultiPortalDesktopRoutes:*",
|
|
111
|
+
"rolesMultiPortalRoutePolicies:*"
|
|
112
|
+
];
|
|
113
|
+
const EXPLICIT_DESKTOP_ROUTE_UI_LAYOUTS = Symbol("explicitDesktopRouteUiLayouts");
|
|
114
|
+
const SKIP_CREATE_PORTAL_DIRECTORY = Symbol("skipCreatePortalDirectory");
|
|
115
|
+
function getRecordField(record, field) {
|
|
116
|
+
if (!record || typeof record !== "object") {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const maybeModel = record;
|
|
120
|
+
if (typeof maybeModel.get === "function") {
|
|
121
|
+
return maybeModel.get(field);
|
|
122
|
+
}
|
|
123
|
+
return record[field];
|
|
124
|
+
}
|
|
125
|
+
function isRecordLike(value) {
|
|
126
|
+
return !!value && typeof value === "object";
|
|
127
|
+
}
|
|
128
|
+
async function pathExists(filePath) {
|
|
129
|
+
try {
|
|
130
|
+
await import_fs.default.promises.access(filePath);
|
|
131
|
+
return true;
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function trimString(value) {
|
|
137
|
+
return String(value ?? "").trim();
|
|
138
|
+
}
|
|
139
|
+
function isInitPortalType(value) {
|
|
140
|
+
return INIT_PORTAL_TYPES.includes(value);
|
|
141
|
+
}
|
|
142
|
+
function getInitPortalType() {
|
|
143
|
+
const portalType = trimString(process.env.INIT_PORTAL_TYPE) || DEFAULT_INIT_PORTAL_TYPE;
|
|
144
|
+
if (!isInitPortalType(portalType)) {
|
|
145
|
+
throw new Error('INIT_PORTAL_TYPE must be either "no-code" or "ai".');
|
|
146
|
+
}
|
|
147
|
+
return portalType;
|
|
148
|
+
}
|
|
149
|
+
function getInitPortalName() {
|
|
150
|
+
const portalName = trimString(process.env.INIT_PORTAL_NAME) || DEFAULT_INIT_PORTAL_NAME;
|
|
151
|
+
if (!MULTI_PORTAL_SLUG_PATTERN.test(portalName)) {
|
|
152
|
+
throw new Error("INIT_PORTAL_NAME can only contain lowercase letters, numbers, hyphens, and underscores.");
|
|
153
|
+
}
|
|
154
|
+
return portalName;
|
|
155
|
+
}
|
|
156
|
+
function getInitPortalTemplate() {
|
|
157
|
+
return trimString(process.env.INIT_PORTAL_TEMPLATE) || DEFAULT_INIT_PORTAL_TEMPLATE;
|
|
158
|
+
}
|
|
159
|
+
function formatInitPortalTitle(portalName) {
|
|
160
|
+
const title = portalName.split(/[-_]/).filter(Boolean).map((segment) => `${segment.charAt(0).toUpperCase()}${segment.slice(1)}`).join(" ");
|
|
161
|
+
return title || portalName;
|
|
162
|
+
}
|
|
163
|
+
function getDefaultMultiPortalRecord() {
|
|
164
|
+
const portalName = getInitPortalName();
|
|
165
|
+
return {
|
|
166
|
+
uid: DEFAULT_MULTI_PORTAL_UID,
|
|
167
|
+
title: formatInitPortalTitle(portalName),
|
|
168
|
+
icon: "DesktopOutlined",
|
|
169
|
+
portalType: getInitPortalType(),
|
|
170
|
+
portalName,
|
|
171
|
+
routePath: `/${portalName}`,
|
|
172
|
+
authCheck: true,
|
|
173
|
+
enabled: true,
|
|
174
|
+
uiLayoutUid: "admin-layout-model"
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function isValidPortalDeploySegment(value) {
|
|
178
|
+
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
179
|
+
}
|
|
180
|
+
function normalizePortalStorageName(value) {
|
|
181
|
+
return trimString(value).replace(/^\/+|\/+$/g, "");
|
|
182
|
+
}
|
|
183
|
+
function normalizePortalStoragePath(value, fallbackName) {
|
|
184
|
+
const segment = trimString(value).replace(/^\/+|\/+$/g, "");
|
|
185
|
+
return `/${segment || fallbackName}`;
|
|
186
|
+
}
|
|
187
|
+
function resolvePortalStoragePublicPath(value) {
|
|
188
|
+
const normalized = trimString(value) || "/";
|
|
189
|
+
const withLeadingSlash = normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
190
|
+
return withLeadingSlash.endsWith("/") ? withLeadingSlash : `${withLeadingSlash}/`;
|
|
191
|
+
}
|
|
192
|
+
function joinPortalStoragePublicPath(publicPath, pathname) {
|
|
193
|
+
const normalizedPublicPath = resolvePortalStoragePublicPath(publicPath);
|
|
194
|
+
const normalizedPathname = normalizePortalStoragePath(pathname, "").replace(/^\/+/, "");
|
|
195
|
+
return `${normalizedPublicPath.replace(/\/$/, "")}/${normalizedPathname}`.replace(/^\/{2,}/, "/");
|
|
196
|
+
}
|
|
197
|
+
function isAbsoluteUrl(value) {
|
|
198
|
+
return /^[a-z][a-z\d+\-.]*:\/\//i.test(value) || value.startsWith("//");
|
|
199
|
+
}
|
|
200
|
+
function appendPortalStorageSubAppApiUrl(apiUrl, appName) {
|
|
201
|
+
if (appName === MAIN_APP_NAME) {
|
|
202
|
+
return apiUrl;
|
|
203
|
+
}
|
|
204
|
+
const subAppApiPath = `/__app/${encodeURIComponent(appName)}`;
|
|
205
|
+
const subAppApiPathPattern = new RegExp(`/__app/${appName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/?$`);
|
|
206
|
+
if (subAppApiPathPattern.test(apiUrl)) {
|
|
207
|
+
return apiUrl;
|
|
208
|
+
}
|
|
209
|
+
if (isAbsoluteUrl(apiUrl)) {
|
|
210
|
+
const url = new URL(apiUrl.startsWith("//") ? `http:${apiUrl}` : apiUrl);
|
|
211
|
+
if (!subAppApiPathPattern.test(url.pathname)) {
|
|
212
|
+
url.pathname = `${url.pathname.replace(/\/+$/, "")}${subAppApiPath}`;
|
|
213
|
+
}
|
|
214
|
+
const value = url.toString();
|
|
215
|
+
return apiUrl.startsWith("//") ? value.replace(/^http:/, "") : value;
|
|
216
|
+
}
|
|
217
|
+
return `${apiUrl.replace(/\/+$/, "")}${subAppApiPath}`;
|
|
218
|
+
}
|
|
219
|
+
function getPortalStorageApiUrl(appName) {
|
|
220
|
+
const configuredApiUrl = trimString(
|
|
221
|
+
process.env.NOCOBASE_API_URL || process.env.API_BASE_URL || process.env.API_BASE_PATH
|
|
222
|
+
);
|
|
223
|
+
const apiUrl = configuredApiUrl || "/api";
|
|
224
|
+
if (isAbsoluteUrl(apiUrl)) {
|
|
225
|
+
return appendPortalStorageSubAppApiUrl(apiUrl, appName);
|
|
226
|
+
}
|
|
227
|
+
const normalizedApiPath = normalizePortalStoragePath(apiUrl, "api");
|
|
228
|
+
const appPublicPath = resolvePortalStoragePublicPath(process.env.APP_PUBLIC_PATH || "/");
|
|
229
|
+
if (appPublicPath === "/" || normalizedApiPath === appPublicPath.slice(0, -1)) {
|
|
230
|
+
return appendPortalStorageSubAppApiUrl(normalizedApiPath, appName);
|
|
231
|
+
}
|
|
232
|
+
if (normalizedApiPath.startsWith(appPublicPath)) {
|
|
233
|
+
return appendPortalStorageSubAppApiUrl(normalizedApiPath, appName);
|
|
234
|
+
}
|
|
235
|
+
return appendPortalStorageSubAppApiUrl(joinPortalStoragePublicPath(appPublicPath, normalizedApiPath), appName);
|
|
236
|
+
}
|
|
237
|
+
function getPortalDeployBasePath(appName, portalName) {
|
|
238
|
+
const portalPath = appName === MAIN_APP_NAME ? `/${PORTAL_CLIENT_PREFIX}/${portalName}/` : `/${PORTAL_CLIENT_PREFIX}/apps/${appName}/${portalName}/`;
|
|
239
|
+
return resolvePortalStoragePublicPath(joinPortalStoragePublicPath(process.env.APP_PUBLIC_PATH || "/", portalPath));
|
|
240
|
+
}
|
|
241
|
+
function createPortalDeployUploadMiddleware() {
|
|
242
|
+
const storage = import_utils.koaMulter.diskStorage({
|
|
243
|
+
destination: import_os.default.tmpdir(),
|
|
244
|
+
filename: function(_req, _file, cb) {
|
|
245
|
+
const randomName = Date.now().toString() + Math.random().toString().slice(2);
|
|
246
|
+
cb(null, randomName);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const upload = (0, import_utils.koaMulter)({
|
|
250
|
+
storage,
|
|
251
|
+
limits: {
|
|
252
|
+
fileSize: PORTAL_DEPLOY_UPLOAD_LIMIT
|
|
253
|
+
}
|
|
254
|
+
}).single("file");
|
|
255
|
+
return async (ctx, next) => {
|
|
256
|
+
if (ctx.action.resourceName !== "multiPortals" || ctx.action.actionName !== "deploy" && ctx.action.actionName !== "pushSource") {
|
|
257
|
+
await next();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
await upload(ctx, next);
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function isValidPortalStorageName(value) {
|
|
264
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value);
|
|
265
|
+
}
|
|
266
|
+
function getPortalStorageLogPath(item) {
|
|
267
|
+
return (0, import_utils.storagePathJoin)("logs", "portals", item.appName, `${item.portalName}.log`);
|
|
268
|
+
}
|
|
269
|
+
function getPortalStorageLogRelativePath(item) {
|
|
270
|
+
return import_path.default.join("logs", "portals", item.appName, `${item.portalName}.log`);
|
|
271
|
+
}
|
|
272
|
+
async function ensurePortalStorageLogFile(logPath) {
|
|
273
|
+
await import_fs.default.promises.mkdir(import_path.default.dirname(logPath), { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
async function appendPortalStorageLog(logPath, message) {
|
|
276
|
+
await ensurePortalStorageLogFile(logPath);
|
|
277
|
+
await import_fs.default.promises.appendFile(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}
|
|
278
|
+
`, "utf-8");
|
|
279
|
+
}
|
|
280
|
+
function formatPortalStorageCommand(command, args) {
|
|
281
|
+
return [command, ...args].join(" ");
|
|
282
|
+
}
|
|
283
|
+
function getLocalPortalTemplatePath(templateSource) {
|
|
284
|
+
if (templateSource.startsWith("file://")) {
|
|
285
|
+
try {
|
|
286
|
+
return (0, import_url.fileURLToPath)(templateSource);
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (import_path.default.isAbsolute(templateSource) || templateSource.startsWith("./") || templateSource.startsWith("../")) {
|
|
292
|
+
return import_path.default.resolve(templateSource);
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
async function resolveLocalPortalTemplate(templateSource) {
|
|
297
|
+
const localPath = getLocalPortalTemplatePath(templateSource);
|
|
298
|
+
if (!localPath) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
let stat;
|
|
302
|
+
try {
|
|
303
|
+
stat = await import_fs.default.promises.stat(localPath);
|
|
304
|
+
} catch {
|
|
305
|
+
throw new Error(`Portal template "${templateSource}" is invalid: local directory does not exist.`);
|
|
306
|
+
}
|
|
307
|
+
if (!stat.isDirectory()) {
|
|
308
|
+
throw new Error(`Portal template "${templateSource}" is invalid: expected a directory.`);
|
|
309
|
+
}
|
|
310
|
+
if (!await pathExists(import_path.default.join(localPath, "package.json"))) {
|
|
311
|
+
throw new Error(`Portal template "${templateSource}" is invalid: package.json is missing.`);
|
|
312
|
+
}
|
|
313
|
+
return { dir: localPath };
|
|
314
|
+
}
|
|
315
|
+
function resolveInstalledPortalTemplatePackage(templatePackage) {
|
|
316
|
+
try {
|
|
317
|
+
return { dir: import_path.default.dirname(require.resolve(`${templatePackage}/package.json`)) };
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function resolvePackedPortalTemplateTarball(packRoot, templatePackage) {
|
|
323
|
+
const entries = await import_fs.default.promises.readdir(packRoot, { withFileTypes: true });
|
|
324
|
+
const tarballs = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".tgz")).map((entry) => import_path.default.join(packRoot, entry.name)).sort();
|
|
325
|
+
if (tarballs.length === 1) {
|
|
326
|
+
return tarballs[0];
|
|
327
|
+
}
|
|
328
|
+
if (!tarballs.length) {
|
|
329
|
+
throw new Error(`npm pack did not produce a local tarball for ${templatePackage}.`);
|
|
330
|
+
}
|
|
331
|
+
throw new Error(`npm pack produced multiple tarballs for ${templatePackage}.`);
|
|
332
|
+
}
|
|
333
|
+
async function downloadPortalTemplatePackage(templatePackage, logPath) {
|
|
334
|
+
const packRoot = await import_fs.default.promises.mkdtemp(import_path.default.join(import_os.default.tmpdir(), "nocobase-portal-template-pack-"));
|
|
335
|
+
const extractRoot = await import_fs.default.promises.mkdtemp(import_path.default.join(import_os.default.tmpdir(), "nocobase-portal-template-extract-"));
|
|
336
|
+
let cleanupPackRoot = true;
|
|
337
|
+
let cleanupExtractRoot = true;
|
|
338
|
+
try {
|
|
339
|
+
await runPortalStorageCommandOnce("npm", ["pack", "--silent", templatePackage], {
|
|
340
|
+
cwd: packRoot,
|
|
341
|
+
env: getPortalStorageCommandEnv(),
|
|
342
|
+
logPath,
|
|
343
|
+
timeoutMs: PORTAL_TEMPLATE_NPM_PACK_TIMEOUT_MS
|
|
344
|
+
});
|
|
345
|
+
const tarballPath = await resolvePackedPortalTemplateTarball(packRoot, templatePackage);
|
|
346
|
+
await (0, import_promises.pipeline)(import_fs.default.createReadStream(tarballPath), (0, import_zlib.createGunzip)(), tar.extract({ cwd: extractRoot, strip: 1 }));
|
|
347
|
+
if (!await pathExists(import_path.default.join(extractRoot, "package.json"))) {
|
|
348
|
+
throw new Error(`Portal template package "${templatePackage}" is invalid: package.json is missing.`);
|
|
349
|
+
}
|
|
350
|
+
cleanupPackRoot = false;
|
|
351
|
+
cleanupExtractRoot = false;
|
|
352
|
+
return {
|
|
353
|
+
dir: extractRoot,
|
|
354
|
+
cleanup: async () => {
|
|
355
|
+
await import_fs.default.promises.rm(packRoot, { recursive: true, force: true });
|
|
356
|
+
await import_fs.default.promises.rm(extractRoot, { recursive: true, force: true });
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
} catch (error) {
|
|
360
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
361
|
+
throw new Error(`Failed to download portal template package "${templatePackage}" with npm pack. ${message}`);
|
|
362
|
+
} finally {
|
|
363
|
+
if (cleanupPackRoot) {
|
|
364
|
+
await import_fs.default.promises.rm(packRoot, { recursive: true, force: true });
|
|
365
|
+
}
|
|
366
|
+
if (cleanupExtractRoot) {
|
|
367
|
+
await import_fs.default.promises.rm(extractRoot, { recursive: true, force: true });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
async function resolvePortalTemplate(templateSource, logPath) {
|
|
372
|
+
const localTemplate = await resolveLocalPortalTemplate(templateSource);
|
|
373
|
+
if (localTemplate) {
|
|
374
|
+
return localTemplate;
|
|
375
|
+
}
|
|
376
|
+
const installedTemplate = resolveInstalledPortalTemplatePackage(templateSource);
|
|
377
|
+
if (installedTemplate) {
|
|
378
|
+
return installedTemplate;
|
|
379
|
+
}
|
|
380
|
+
return downloadPortalTemplatePackage(templateSource, logPath);
|
|
381
|
+
}
|
|
382
|
+
async function copyPortalTemplate(sourceDir, targetDir) {
|
|
383
|
+
const ignoredSegments = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store"]);
|
|
384
|
+
await import_fs.default.promises.mkdir(import_path.default.dirname(targetDir), { recursive: true });
|
|
385
|
+
await import_fs.default.promises.cp(sourceDir, targetDir, {
|
|
386
|
+
recursive: true,
|
|
387
|
+
filter: (source) => !import_path.default.relative(sourceDir, source).split(import_path.default.sep).some((segment) => segment.startsWith("._") || ignoredSegments.has(segment))
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function sanitizePortalStorageNodeOptions(value) {
|
|
391
|
+
return trimString(value).split(/\s+/).filter((option) => option !== "--preserve-symlinks" && option !== "--preserve-symlinks-main").join(" ");
|
|
392
|
+
}
|
|
393
|
+
function getPortalStorageCommandEnv(env = {}) {
|
|
394
|
+
const commandEnv = { ...process.env, ...env };
|
|
395
|
+
const nodeOptions = sanitizePortalStorageNodeOptions(commandEnv.NODE_OPTIONS);
|
|
396
|
+
if (nodeOptions) {
|
|
397
|
+
commandEnv.NODE_OPTIONS = nodeOptions;
|
|
398
|
+
} else {
|
|
399
|
+
delete commandEnv.NODE_OPTIONS;
|
|
400
|
+
}
|
|
401
|
+
return commandEnv;
|
|
402
|
+
}
|
|
403
|
+
async function runPortalStorageCommandOnce(command, args, options) {
|
|
404
|
+
var _a, _b;
|
|
405
|
+
await appendPortalStorageLog(options.logPath, `Running command: ${formatPortalStorageCommand(command, args)}`);
|
|
406
|
+
await ensurePortalStorageLogFile(options.logPath);
|
|
407
|
+
const logStream = import_fs.default.createWriteStream(options.logPath, { flags: "a" });
|
|
408
|
+
const subprocess = (0, import_child_process.spawn)(command, args, {
|
|
409
|
+
cwd: options.cwd,
|
|
410
|
+
env: options.env,
|
|
411
|
+
shell: false
|
|
412
|
+
});
|
|
413
|
+
let timeout;
|
|
414
|
+
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
415
|
+
timeout = setTimeout(() => {
|
|
416
|
+
var _a2;
|
|
417
|
+
(_a2 = subprocess.kill) == null ? void 0 : _a2.call(subprocess, "SIGTERM");
|
|
418
|
+
}, options.timeoutMs);
|
|
419
|
+
}
|
|
420
|
+
(_a = subprocess.stdout) == null ? void 0 : _a.pipe(logStream, { end: false });
|
|
421
|
+
(_b = subprocess.stderr) == null ? void 0 : _b.pipe(logStream, { end: false });
|
|
422
|
+
let result;
|
|
423
|
+
try {
|
|
424
|
+
result = await new Promise((resolve, reject) => {
|
|
425
|
+
subprocess.once("error", reject);
|
|
426
|
+
subprocess.once("close", (code, signal) => {
|
|
427
|
+
if (timeout) {
|
|
428
|
+
clearTimeout(timeout);
|
|
429
|
+
}
|
|
430
|
+
resolve({ code, signal });
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
await new Promise((resolve, reject) => {
|
|
434
|
+
const handleError = (error) => {
|
|
435
|
+
reject(error);
|
|
436
|
+
};
|
|
437
|
+
logStream.once("error", handleError);
|
|
438
|
+
logStream.end(() => {
|
|
439
|
+
logStream.off("error", handleError);
|
|
440
|
+
resolve();
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (timeout) {
|
|
445
|
+
clearTimeout(timeout);
|
|
446
|
+
}
|
|
447
|
+
logStream.end();
|
|
448
|
+
await appendPortalStorageLog(options.logPath, `Command failed to start: ${command}`);
|
|
449
|
+
throw error;
|
|
450
|
+
}
|
|
451
|
+
await appendPortalStorageLog(
|
|
452
|
+
options.logPath,
|
|
453
|
+
`Command finished: ${formatPortalStorageCommand(command, args)} exitCode=${result.code ?? ""} signal=${result.signal ?? ""}`
|
|
454
|
+
);
|
|
455
|
+
if (result.signal) {
|
|
456
|
+
const error = new Error(
|
|
457
|
+
`Command failed with signal ${result.signal}: ${formatPortalStorageCommand(command, args)}`
|
|
458
|
+
);
|
|
459
|
+
error.code = result.code;
|
|
460
|
+
error.signal = result.signal;
|
|
461
|
+
error.cmd = formatPortalStorageCommand(command, args);
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
if (result.code && result.code !== 0) {
|
|
465
|
+
const error = new Error(
|
|
466
|
+
`Command failed with exit code ${result.code}: ${formatPortalStorageCommand(command, args)}`
|
|
467
|
+
);
|
|
468
|
+
error.code = result.code;
|
|
469
|
+
error.signal = result.signal;
|
|
470
|
+
error.cmd = formatPortalStorageCommand(command, args);
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
async function buildPortalStorageItem(portalDir, item) {
|
|
475
|
+
const logPath = getPortalStorageLogPath(item);
|
|
476
|
+
const buildEnv = getPortalStorageCommandEnv({
|
|
477
|
+
NOCOBASE_API_URL: getPortalStorageApiUrl(item.appName),
|
|
478
|
+
NOCOBASE_PORTAL_BASE: getPortalDeployBasePath(item.appName, item.portalName)
|
|
479
|
+
});
|
|
480
|
+
await appendPortalStorageLog(logPath, `Building portal ${item.appName}/${item.portalName}.`);
|
|
481
|
+
await appendPortalStorageLog(
|
|
482
|
+
logPath,
|
|
483
|
+
`Build environment: NOCOBASE_API_URL=${buildEnv.NOCOBASE_API_URL || ""} NOCOBASE_PORTAL_BASE=${buildEnv.NOCOBASE_PORTAL_BASE || ""} APP_PUBLIC_PATH=${buildEnv.APP_PUBLIC_PATH || ""}`
|
|
484
|
+
);
|
|
485
|
+
await runPortalStorageCommandOnce("yarn", ["build:html"], {
|
|
486
|
+
cwd: portalDir,
|
|
487
|
+
env: buildEnv,
|
|
488
|
+
logPath
|
|
489
|
+
});
|
|
490
|
+
await appendPortalStorageLog(logPath, `Portal build completed for ${item.appName}/${item.portalName}.`);
|
|
491
|
+
}
|
|
492
|
+
function validatePortalDeployBasePath(appName, portalName, basePath) {
|
|
493
|
+
const normalizedBasePath = resolvePortalStoragePublicPath(basePath);
|
|
494
|
+
if (normalizedBasePath.includes("..")) {
|
|
495
|
+
throw new Error('basePath cannot contain ".."');
|
|
496
|
+
}
|
|
497
|
+
const expectedBasePath = getPortalDeployBasePath(appName, portalName);
|
|
498
|
+
if (normalizedBasePath !== expectedBasePath) {
|
|
499
|
+
throw new Error(`basePath must be ${expectedBasePath}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function isPortalDeployTarEntry(entry) {
|
|
503
|
+
return Boolean(entry) && typeof entry === "object";
|
|
504
|
+
}
|
|
505
|
+
function validatePortalDeployTarEntry(entryPath, entry) {
|
|
506
|
+
const normalizedEntryPath = import_path.default.normalize(entryPath);
|
|
507
|
+
if (import_path.default.isAbsolute(entryPath) || normalizedEntryPath === ".." || normalizedEntryPath.startsWith(`..${import_path.default.sep}`) || normalizedEntryPath.split(import_path.default.sep).includes("..")) {
|
|
508
|
+
throw new Error(`Invalid dist archive entry path: ${entryPath}`);
|
|
509
|
+
}
|
|
510
|
+
const entryType = isPortalDeployTarEntry(entry) ? entry.type : void 0;
|
|
511
|
+
if (entryType === "SymbolicLink" || entryType === "Link") {
|
|
512
|
+
throw new Error(`Invalid dist archive link entry: ${entryPath}`);
|
|
513
|
+
}
|
|
514
|
+
const linkpath = isPortalDeployTarEntry(entry) ? entry.linkpath : void 0;
|
|
515
|
+
if (linkpath) {
|
|
516
|
+
throw new Error(`Invalid dist archive link target: ${entryPath}`);
|
|
517
|
+
}
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
function isPortalDeployStorageTempDir(name) {
|
|
521
|
+
return name.startsWith(".dist-upload-") || name.startsWith(".dist-backup-");
|
|
522
|
+
}
|
|
523
|
+
function isNodeFsError(error) {
|
|
524
|
+
return error instanceof Error && "code" in error;
|
|
525
|
+
}
|
|
526
|
+
async function cleanupInterruptedPortalDeployDirs(portalDir) {
|
|
527
|
+
let entries;
|
|
528
|
+
try {
|
|
529
|
+
entries = await import_fs.default.promises.readdir(portalDir, { withFileTypes: true });
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (isNodeFsError(error) && error.code === "ENOENT") {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
await Promise.all(
|
|
537
|
+
entries.filter((entry) => entry.isDirectory() && isPortalDeployStorageTempDir(entry.name)).map((entry) => import_fs.default.promises.rm(import_path.default.join(portalDir, entry.name), { recursive: true, force: true }))
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
async function movePortalDeployDir(sourceDir, targetDir) {
|
|
541
|
+
try {
|
|
542
|
+
await import_fs.default.promises.rename(sourceDir, targetDir);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
if (!isNodeFsError(error) || error.code !== "EXDEV") {
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
await import_fs.default.promises.cp(sourceDir, targetDir, { recursive: true });
|
|
548
|
+
await import_fs.default.promises.rm(sourceDir, { recursive: true, force: true });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function chmodPortalDistTree(targetDir) {
|
|
552
|
+
await import_fs.default.promises.chmod(targetDir, PORTAL_PUBLIC_DIR_MODE);
|
|
553
|
+
const entries = await import_fs.default.promises.readdir(targetDir, { withFileTypes: true });
|
|
554
|
+
await Promise.all(
|
|
555
|
+
entries.map(async (entry) => {
|
|
556
|
+
const entryPath = import_path.default.join(targetDir, entry.name);
|
|
557
|
+
if (entry.isDirectory()) {
|
|
558
|
+
await chmodPortalDistTree(entryPath);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (entry.isFile()) {
|
|
562
|
+
await import_fs.default.promises.chmod(entryPath, PORTAL_PUBLIC_FILE_MODE);
|
|
563
|
+
}
|
|
564
|
+
})
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
async function ensurePortalDistPublicReadable(portalDir, distDir) {
|
|
568
|
+
const appDir = import_path.default.dirname(portalDir);
|
|
569
|
+
const portalsDir = import_path.default.dirname(appDir);
|
|
570
|
+
await import_fs.default.promises.chmod(portalsDir, PORTAL_PUBLIC_DIR_MODE);
|
|
571
|
+
await import_fs.default.promises.chmod(appDir, PORTAL_PUBLIC_DIR_MODE);
|
|
572
|
+
await import_fs.default.promises.chmod(portalDir, PORTAL_PUBLIC_DIR_MODE);
|
|
573
|
+
await chmodPortalDistTree(distDir);
|
|
574
|
+
}
|
|
575
|
+
async function replacePortalDistFromArchive(params) {
|
|
576
|
+
const portalDir = (0, import_utils.storagePathJoin)("portals", params.appName, params.portalName);
|
|
577
|
+
const distDir = import_path.default.join(portalDir, "dist");
|
|
578
|
+
const backupDir = import_path.default.join(portalDir, `.dist-backup-${Date.now()}-${Math.random().toString().slice(2)}`);
|
|
579
|
+
const tarPath = import_path.default.join(import_os.default.tmpdir(), `nocobase-portal-dist-${Date.now()}-${Math.random().toString().slice(2)}.tar`);
|
|
580
|
+
const uploadDir = await import_fs.default.promises.mkdtemp(import_path.default.join(import_os.default.tmpdir(), PORTAL_DEPLOY_UPLOAD_DIR_PREFIX));
|
|
581
|
+
let hasBackup = false;
|
|
582
|
+
await import_fs.default.promises.mkdir(portalDir, { recursive: true });
|
|
583
|
+
await cleanupInterruptedPortalDeployDirs(portalDir);
|
|
584
|
+
try {
|
|
585
|
+
await (0, import_promises.pipeline)(import_fs.default.createReadStream(params.filePath), (0, import_zlib.createGunzip)(), import_fs.default.createWriteStream(tarPath));
|
|
586
|
+
await tar.extract({
|
|
587
|
+
file: tarPath,
|
|
588
|
+
cwd: uploadDir,
|
|
589
|
+
strict: true,
|
|
590
|
+
filter: validatePortalDeployTarEntry
|
|
591
|
+
});
|
|
592
|
+
const indexPath = import_path.default.join(uploadDir, "index.html");
|
|
593
|
+
const indexStat = await import_fs.default.promises.stat(indexPath).catch(() => null);
|
|
594
|
+
if (!(indexStat == null ? void 0 : indexStat.isFile())) {
|
|
595
|
+
throw new Error("Portal dist archive is invalid: index.html is missing.");
|
|
596
|
+
}
|
|
597
|
+
if (await pathExists(distDir)) {
|
|
598
|
+
await import_fs.default.promises.rename(distDir, backupDir);
|
|
599
|
+
hasBackup = true;
|
|
600
|
+
}
|
|
601
|
+
await movePortalDeployDir(uploadDir, distDir);
|
|
602
|
+
await ensurePortalDistPublicReadable(portalDir, distDir);
|
|
603
|
+
if (hasBackup) {
|
|
604
|
+
await import_fs.default.promises.rm(backupDir, { recursive: true, force: true });
|
|
605
|
+
hasBackup = false;
|
|
606
|
+
}
|
|
607
|
+
return import_path.default.relative((0, import_utils.storagePathJoin)(), distDir);
|
|
608
|
+
} catch (error) {
|
|
609
|
+
await import_fs.default.promises.rm(uploadDir, { recursive: true, force: true });
|
|
610
|
+
if (hasBackup) {
|
|
611
|
+
await import_fs.default.promises.rm(distDir, { recursive: true, force: true });
|
|
612
|
+
await import_fs.default.promises.rename(backupDir, distDir).catch(async () => {
|
|
613
|
+
await import_fs.default.promises.rm(backupDir, { recursive: true, force: true });
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
throw error;
|
|
617
|
+
} finally {
|
|
618
|
+
await import_fs.default.promises.rm(params.filePath, { force: true });
|
|
619
|
+
await import_fs.default.promises.rm(uploadDir, { recursive: true, force: true });
|
|
620
|
+
await import_fs.default.promises.rm(tarPath, { force: true });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function shouldPackPortalSourceEntry(entryName) {
|
|
624
|
+
return !entryName.split("/").some((segment) => segment.startsWith("._") || [".git", "node_modules", "dist", ".DS_Store"].includes(segment));
|
|
625
|
+
}
|
|
626
|
+
function validatePortalSourceTarEntry(entryPath, entry) {
|
|
627
|
+
if (import_path.default.isAbsolute(entryPath) || entryPath.split(/[\\/]+/).includes("..")) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
const entryType = isPortalDeployTarEntry(entry) ? entry.type : void 0;
|
|
631
|
+
const linkpath = isPortalDeployTarEntry(entry) ? entry.linkpath : void 0;
|
|
632
|
+
if (entryType === "SymbolicLink" || entryType === "Link" || typeof linkpath === "string") {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
return shouldPackPortalSourceEntry(entryPath);
|
|
636
|
+
}
|
|
637
|
+
async function packPortalSource(params) {
|
|
638
|
+
const portalDir = (0, import_utils.storagePathJoin)("portals", params.appName, params.portalName);
|
|
639
|
+
if (!await pathExists(portalDir)) {
|
|
640
|
+
throw new Error("Portal source directory does not exist.");
|
|
641
|
+
}
|
|
642
|
+
const archivePath = import_path.default.join(
|
|
643
|
+
import_os.default.tmpdir(),
|
|
644
|
+
`nocobase-portal-source-${Date.now()}-${Math.random().toString().slice(2)}.tar.gz`
|
|
645
|
+
);
|
|
646
|
+
await new Promise((resolve, reject) => {
|
|
647
|
+
var _a;
|
|
648
|
+
const child = (0, import_child_process.spawn)(
|
|
649
|
+
"tar",
|
|
650
|
+
[
|
|
651
|
+
"-czf",
|
|
652
|
+
archivePath,
|
|
653
|
+
"--exclude=.git",
|
|
654
|
+
"--exclude=node_modules",
|
|
655
|
+
"--exclude=dist",
|
|
656
|
+
"--exclude=.DS_Store",
|
|
657
|
+
"--exclude=._*",
|
|
658
|
+
"-C",
|
|
659
|
+
portalDir,
|
|
660
|
+
"."
|
|
661
|
+
],
|
|
662
|
+
{
|
|
663
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
664
|
+
}
|
|
665
|
+
);
|
|
666
|
+
let stderr = "";
|
|
667
|
+
(_a = child.stderr) == null ? void 0 : _a.on("data", (chunk) => {
|
|
668
|
+
stderr += String(chunk);
|
|
669
|
+
});
|
|
670
|
+
child.on("error", reject);
|
|
671
|
+
child.on("close", (code) => {
|
|
672
|
+
if (code === 0) {
|
|
673
|
+
resolve();
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
reject(new Error(`tar exited with code ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
return archivePath;
|
|
680
|
+
}
|
|
681
|
+
async function replacePortalSourceFromArchive(params) {
|
|
682
|
+
const portalDir = (0, import_utils.storagePathJoin)("portals", params.appName, params.portalName);
|
|
683
|
+
const tarPath = import_path.default.join(
|
|
684
|
+
import_os.default.tmpdir(),
|
|
685
|
+
`nocobase-portal-source-${Date.now()}-${Math.random().toString().slice(2)}.tar`
|
|
686
|
+
);
|
|
687
|
+
const uploadDir = await import_fs.default.promises.mkdtemp(import_path.default.join(import_os.default.tmpdir(), "nocobase-portal-source-upload-"));
|
|
688
|
+
try {
|
|
689
|
+
await (0, import_promises.pipeline)(import_fs.default.createReadStream(params.filePath), (0, import_zlib.createGunzip)(), import_fs.default.createWriteStream(tarPath));
|
|
690
|
+
await tar.extract({
|
|
691
|
+
file: tarPath,
|
|
692
|
+
cwd: uploadDir,
|
|
693
|
+
strict: true,
|
|
694
|
+
filter: validatePortalSourceTarEntry
|
|
695
|
+
});
|
|
696
|
+
await import_fs.default.promises.mkdir(portalDir, { recursive: true });
|
|
697
|
+
const existingEntries = await import_fs.default.promises.readdir(portalDir).catch(() => []);
|
|
698
|
+
await Promise.all(
|
|
699
|
+
existingEntries.filter((entry) => shouldPackPortalSourceEntry(entry)).map((entry) => import_fs.default.promises.rm(import_path.default.join(portalDir, entry), { recursive: true, force: true }))
|
|
700
|
+
);
|
|
701
|
+
const sourceEntries = await import_fs.default.promises.readdir(uploadDir);
|
|
702
|
+
await Promise.all(
|
|
703
|
+
sourceEntries.map((entry) => import_fs.default.promises.rename(import_path.default.join(uploadDir, entry), import_path.default.join(portalDir, entry)))
|
|
704
|
+
);
|
|
705
|
+
return import_path.default.relative((0, import_utils.storagePathJoin)(), portalDir);
|
|
706
|
+
} finally {
|
|
707
|
+
await import_fs.default.promises.rm(params.filePath, { force: true });
|
|
708
|
+
await import_fs.default.promises.rm(uploadDir, { recursive: true, force: true });
|
|
709
|
+
await import_fs.default.promises.rm(tarPath, { force: true });
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function isDefaultMultiPortalUid(uid) {
|
|
713
|
+
return typeof uid === "string" && DEFAULT_MULTI_PORTAL_UID_SET.has(uid);
|
|
714
|
+
}
|
|
715
|
+
function getDefaultMultiPortalRecordForUid(uid) {
|
|
716
|
+
return isDefaultMultiPortalUid(uid) ? getDefaultMultiPortalRecord() : void 0;
|
|
717
|
+
}
|
|
718
|
+
function getDefaultMultiPortalUiLayoutUid(portal) {
|
|
719
|
+
return Object.prototype.hasOwnProperty.call(portal, "uiLayoutUid") ? portal.uiLayoutUid ?? null : null;
|
|
720
|
+
}
|
|
721
|
+
function withCustomMultiPortalFilter(filter = {}) {
|
|
722
|
+
const customFilter = { ...filter };
|
|
723
|
+
const uidFilter = customFilter.uid;
|
|
724
|
+
if (Array.isArray(uidFilter)) {
|
|
725
|
+
customFilter.uid = uidFilter.filter((uid) => !isDefaultMultiPortalUid(uid));
|
|
726
|
+
return customFilter;
|
|
727
|
+
}
|
|
728
|
+
if (isDefaultMultiPortalUid(uidFilter)) {
|
|
729
|
+
customFilter.uid = [];
|
|
730
|
+
return customFilter;
|
|
731
|
+
}
|
|
732
|
+
customFilter["uid.$notIn"] = DEFAULT_MULTI_PORTAL_UIDS;
|
|
733
|
+
return customFilter;
|
|
734
|
+
}
|
|
735
|
+
function uniqueDesktopRouteIds(routeIds) {
|
|
736
|
+
const seen = /* @__PURE__ */ new Set();
|
|
737
|
+
return routeIds.filter((routeId) => {
|
|
738
|
+
if (seen.has(routeId)) {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
seen.add(routeId);
|
|
742
|
+
return true;
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function collectDesktopRouteIds(record) {
|
|
746
|
+
if (Array.isArray(record)) {
|
|
747
|
+
return uniqueDesktopRouteIds(record.flatMap((item) => collectDesktopRouteIds(item)));
|
|
748
|
+
}
|
|
749
|
+
const routeId = getRecordField(record, "id");
|
|
750
|
+
const children = getRecordField(record, "children");
|
|
751
|
+
const routeIds = [];
|
|
752
|
+
if (typeof routeId === "string" || typeof routeId === "number") {
|
|
753
|
+
routeIds.push(routeId);
|
|
754
|
+
}
|
|
755
|
+
if (Array.isArray(children)) {
|
|
756
|
+
routeIds.push(...children.flatMap((child) => collectDesktopRouteIds(child)));
|
|
757
|
+
}
|
|
758
|
+
return uniqueDesktopRouteIds(routeIds);
|
|
759
|
+
}
|
|
760
|
+
function pickMultiPortalUiLayoutRuntimeFields(record) {
|
|
761
|
+
const result = {};
|
|
762
|
+
for (const field of MULTI_PORTAL_UI_LAYOUT_RUNTIME_FIELDS) {
|
|
763
|
+
result[field] = getRecordField(record, field);
|
|
764
|
+
}
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
function pickMultiPortalRuntimeFields(record) {
|
|
768
|
+
const result = {};
|
|
769
|
+
for (const field of MULTI_PORTAL_RUNTIME_FIELDS) {
|
|
770
|
+
result[field] = getRecordField(record, field);
|
|
771
|
+
}
|
|
772
|
+
result.uiLayout = pickMultiPortalUiLayoutRuntimeFields(getRecordField(record, "uiLayout"));
|
|
773
|
+
return result;
|
|
774
|
+
}
|
|
775
|
+
function pickMultiPortalAccessibleFields(record) {
|
|
776
|
+
const result = {};
|
|
777
|
+
for (const field of MULTI_PORTAL_ACCESSIBLE_FIELDS) {
|
|
778
|
+
result[field] = getRecordField(record, field) ?? null;
|
|
779
|
+
}
|
|
780
|
+
result.uiLayout = pickMultiPortalUiLayoutRuntimeFields(getRecordField(record, "uiLayout"));
|
|
781
|
+
return result;
|
|
782
|
+
}
|
|
783
|
+
function getExplicitRequestedLayoutUid(layout) {
|
|
784
|
+
const uid = Array.isArray(layout) ? layout[0] : layout;
|
|
785
|
+
if (typeof uid === "string" && uid.trim()) {
|
|
786
|
+
return uid;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function getRequestedMultiPortalUid(ctx) {
|
|
790
|
+
var _a, _b;
|
|
791
|
+
const portalUid = getExplicitRequestedLayoutUid((_a = ctx.action) == null ? void 0 : _a.params.portal);
|
|
792
|
+
const layoutUid = getExplicitRequestedLayoutUid((_b = ctx.action) == null ? void 0 : _b.params.layout);
|
|
793
|
+
if (portalUid && layoutUid) {
|
|
794
|
+
ctx.throw(400, "layout and portal cannot be used together");
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
return portalUid;
|
|
798
|
+
}
|
|
799
|
+
function getCurrentRoles(ctx) {
|
|
800
|
+
const currentRole = ctx.state.currentRole;
|
|
801
|
+
if (typeof currentRole === "string" && currentRole && currentRole !== UNION_ROLE_KEY) {
|
|
802
|
+
return [currentRole];
|
|
803
|
+
}
|
|
804
|
+
const currentRoles = ctx.state.currentRoles;
|
|
805
|
+
if (Array.isArray(currentRoles)) {
|
|
806
|
+
return currentRoles.filter((role) => typeof role === "string");
|
|
807
|
+
}
|
|
808
|
+
return typeof currentRole === "string" && currentRole ? [currentRole] : [];
|
|
809
|
+
}
|
|
810
|
+
function getMultiPortalNameFromValues(ctx) {
|
|
811
|
+
var _a;
|
|
812
|
+
const values = (_a = ctx.action) == null ? void 0 : _a.params.values;
|
|
813
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const portalName = values.portalName;
|
|
817
|
+
if (typeof portalName === "string" && portalName.trim()) {
|
|
818
|
+
return portalName;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function getMultiPortalWriteValues(ctx) {
|
|
822
|
+
var _a;
|
|
823
|
+
const values = (_a = ctx.action) == null ? void 0 : _a.params.values;
|
|
824
|
+
if (!values || typeof values !== "object" || Array.isArray(values)) {
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
return values;
|
|
828
|
+
}
|
|
829
|
+
function isTruthyActionBoolean(value) {
|
|
830
|
+
return value === true || value === 1 || value === "1" || value === "true";
|
|
831
|
+
}
|
|
832
|
+
async function captureSkipCreatePortalDirectory(ctx, next) {
|
|
833
|
+
const values = getMultiPortalWriteValues(ctx);
|
|
834
|
+
if (!values || !Object.prototype.hasOwnProperty.call(values, "skipCreatePortalDirectory")) {
|
|
835
|
+
await next();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
ctx[SKIP_CREATE_PORTAL_DIRECTORY] = isTruthyActionBoolean(
|
|
839
|
+
values.skipCreatePortalDirectory
|
|
840
|
+
);
|
|
841
|
+
delete values.skipCreatePortalDirectory;
|
|
842
|
+
await next();
|
|
843
|
+
}
|
|
844
|
+
function shouldSkipCreatePortalDirectory(options) {
|
|
845
|
+
var _a;
|
|
846
|
+
return ((_a = options == null ? void 0 : options.context) == null ? void 0 : _a[SKIP_CREATE_PORTAL_DIRECTORY]) === true;
|
|
847
|
+
}
|
|
848
|
+
async function normalizeMultiPortalSlugValues(ctx, next) {
|
|
849
|
+
const values = getMultiPortalWriteValues(ctx);
|
|
850
|
+
const portalName = values == null ? void 0 : values.portalName;
|
|
851
|
+
if (portalName === void 0 || portalName === null) {
|
|
852
|
+
await next();
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (typeof portalName !== "string") {
|
|
856
|
+
await next();
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const slug = portalName.trim();
|
|
860
|
+
if (!slug) {
|
|
861
|
+
await next();
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (!MULTI_PORTAL_SLUG_PATTERN.test(slug)) {
|
|
865
|
+
ctx.throw(400, "Portal name can only contain lowercase letters, numbers, hyphens, and underscores");
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
values.portalName = slug;
|
|
869
|
+
values.routePath = `/${slug}`;
|
|
870
|
+
await next();
|
|
871
|
+
}
|
|
872
|
+
async function ensureDefaultMultiPortals(db, options) {
|
|
873
|
+
const repository = db.getRepository("multiPortals");
|
|
874
|
+
const defaultPortal = getDefaultMultiPortalRecord();
|
|
875
|
+
const existing = await repository.findOne({
|
|
876
|
+
filterByTk: defaultPortal.uid,
|
|
877
|
+
fields: ["uid", "title", "icon", "portalType", "portalName", "routePath", "authCheck", "uiLayoutUid"],
|
|
878
|
+
transaction: options == null ? void 0 : options.transaction
|
|
879
|
+
});
|
|
880
|
+
if (!existing) {
|
|
881
|
+
await repository.create({
|
|
882
|
+
values: defaultPortal,
|
|
883
|
+
transaction: options == null ? void 0 : options.transaction
|
|
884
|
+
});
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const protectedValues = {
|
|
888
|
+
uid: defaultPortal.uid,
|
|
889
|
+
portalType: defaultPortal.portalType ?? null,
|
|
890
|
+
portalName: defaultPortal.portalName,
|
|
891
|
+
routePath: defaultPortal.routePath,
|
|
892
|
+
authCheck: defaultPortal.authCheck,
|
|
893
|
+
uiLayoutUid: getDefaultMultiPortalUiLayoutUid(defaultPortal)
|
|
894
|
+
};
|
|
895
|
+
const shouldRepair = Object.entries(protectedValues).some(([field, value]) => existing.get(field) !== value);
|
|
896
|
+
if (shouldRepair) {
|
|
897
|
+
await repository.update({
|
|
898
|
+
filterByTk: defaultPortal.uid,
|
|
899
|
+
values: protectedValues,
|
|
900
|
+
transaction: options == null ? void 0 : options.transaction
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async function protectDefaultMultiPortalUpdate(ctx, next) {
|
|
905
|
+
var _a, _b, _c;
|
|
906
|
+
const defaultPortal = getDefaultMultiPortalRecordForUid((_a = ctx.action) == null ? void 0 : _a.params.filterByTk);
|
|
907
|
+
if (!defaultPortal) {
|
|
908
|
+
await next();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const params = (_b = ctx.action) == null ? void 0 : _b.params;
|
|
912
|
+
const rawValues = isRecordLike(params == null ? void 0 : params.values) ? params.values : {};
|
|
913
|
+
const values = {
|
|
914
|
+
uid: defaultPortal.uid,
|
|
915
|
+
portalType: defaultPortal.portalType ?? null,
|
|
916
|
+
portalName: defaultPortal.portalName,
|
|
917
|
+
routePath: defaultPortal.routePath,
|
|
918
|
+
authCheck: defaultPortal.authCheck,
|
|
919
|
+
uiLayoutUid: getDefaultMultiPortalUiLayoutUid(defaultPortal)
|
|
920
|
+
};
|
|
921
|
+
if (Object.prototype.hasOwnProperty.call(rawValues, "title")) {
|
|
922
|
+
values.title = rawValues.title;
|
|
923
|
+
}
|
|
924
|
+
if (Object.prototype.hasOwnProperty.call(rawValues, "icon")) {
|
|
925
|
+
values.icon = rawValues.icon;
|
|
926
|
+
}
|
|
927
|
+
if (Object.prototype.hasOwnProperty.call(rawValues, "enabled")) {
|
|
928
|
+
values.enabled = rawValues.enabled;
|
|
929
|
+
}
|
|
930
|
+
(_c = ctx.action) == null ? void 0 : _c.mergeParams(
|
|
931
|
+
{
|
|
932
|
+
values
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
values: "overwrite"
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
await next();
|
|
939
|
+
}
|
|
940
|
+
async function preventUiLayoutRouteNameConflict(ctx, next) {
|
|
941
|
+
var _a;
|
|
942
|
+
if (isDefaultMultiPortalUid((_a = ctx.action) == null ? void 0 : _a.params.filterByTk)) {
|
|
943
|
+
await next();
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const portalName = getMultiPortalNameFromValues(ctx);
|
|
947
|
+
if (!portalName) {
|
|
948
|
+
await next();
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
952
|
+
filter: {
|
|
953
|
+
routeName: portalName
|
|
954
|
+
},
|
|
955
|
+
fields: ["uid"]
|
|
956
|
+
});
|
|
957
|
+
if (uiLayout) {
|
|
958
|
+
ctx.throw(400, "Portal route name conflicts with an existing UI layout");
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
await next();
|
|
962
|
+
}
|
|
963
|
+
async function findRequestedMultiPortal(ctx) {
|
|
964
|
+
var _a;
|
|
965
|
+
const portalUid = getRequestedMultiPortalUid(ctx);
|
|
966
|
+
if (portalUid === void 0) {
|
|
967
|
+
return {
|
|
968
|
+
requested: false
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
const isExplicitPortalRequest = getExplicitRequestedLayoutUid((_a = ctx.action) == null ? void 0 : _a.params.portal) !== void 0;
|
|
972
|
+
if (!isExplicitPortalRequest) {
|
|
973
|
+
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
974
|
+
filter: {
|
|
975
|
+
uid: portalUid
|
|
976
|
+
},
|
|
977
|
+
fields: ["uid"]
|
|
978
|
+
});
|
|
979
|
+
if (uiLayout) {
|
|
980
|
+
return {
|
|
981
|
+
requested: false
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
const portal = await ctx.db.getRepository("multiPortals").findOne({
|
|
986
|
+
filter: {
|
|
987
|
+
uid: portalUid
|
|
988
|
+
},
|
|
989
|
+
fields: ["uid", "uiLayoutUid", "enabled"]
|
|
990
|
+
});
|
|
991
|
+
if ((portal == null ? void 0 : portal.get("enabled")) === true) {
|
|
992
|
+
return {
|
|
993
|
+
portal,
|
|
994
|
+
requested: true
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
return {
|
|
998
|
+
requested: !!isExplicitPortalRequest || !!portal
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function getDesktopRoutePortalFilter(multiPortalUid) {
|
|
1002
|
+
return {
|
|
1003
|
+
"multiPortals.uid": multiPortalUid
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function isDesktopRouteCreateValue(value) {
|
|
1007
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1008
|
+
}
|
|
1009
|
+
function hasDesktopRouteField(value, field) {
|
|
1010
|
+
return !!value && Object.prototype.hasOwnProperty.call(value, field);
|
|
1011
|
+
}
|
|
1012
|
+
function pickExplicitDesktopRouteUiLayouts(value) {
|
|
1013
|
+
if (Array.isArray(value)) {
|
|
1014
|
+
return value.map((item) => pickExplicitDesktopRouteUiLayouts(item));
|
|
1015
|
+
}
|
|
1016
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
const route = {};
|
|
1020
|
+
if (hasDesktopRouteField(value, "uiLayouts")) {
|
|
1021
|
+
route.uiLayouts = value.uiLayouts;
|
|
1022
|
+
}
|
|
1023
|
+
if (Array.isArray(value.children)) {
|
|
1024
|
+
route.children = value.children.map((child) => pickExplicitDesktopRouteUiLayouts(child));
|
|
1025
|
+
}
|
|
1026
|
+
return route;
|
|
1027
|
+
}
|
|
1028
|
+
function withDesktopRouteMultiPortal(value, multiPortalUid, explicitValue = value) {
|
|
1029
|
+
if (Array.isArray(value)) {
|
|
1030
|
+
const explicitValues = Array.isArray(explicitValue) ? explicitValue : [];
|
|
1031
|
+
return value.map((item, index) => withDesktopRouteMultiPortal(item, multiPortalUid, explicitValues[index]));
|
|
1032
|
+
}
|
|
1033
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
1034
|
+
return value;
|
|
1035
|
+
}
|
|
1036
|
+
const explicitRoute = isDesktopRouteCreateValue(explicitValue) ? explicitValue : void 0;
|
|
1037
|
+
const existingMultiPortals = Array.isArray(value.multiPortals) ? value.multiPortals.filter((uid) => typeof uid === "string" && !!uid) : [];
|
|
1038
|
+
const { uiLayouts: _uiLayouts, children: _children, ...route } = value;
|
|
1039
|
+
return {
|
|
1040
|
+
...route,
|
|
1041
|
+
...hasDesktopRouteField(explicitRoute, "uiLayouts") ? { uiLayouts: explicitRoute == null ? void 0 : explicitRoute.uiLayouts } : {},
|
|
1042
|
+
multiPortals: Array.from(/* @__PURE__ */ new Set([...existingMultiPortals, multiPortalUid])),
|
|
1043
|
+
...Array.isArray(value.children) ? { children: withDesktopRouteMultiPortal(value.children, multiPortalUid, explicitRoute == null ? void 0 : explicitRoute.children) } : {}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
async function captureExplicitDesktopRouteUiLayouts(ctx, next) {
|
|
1047
|
+
var _a;
|
|
1048
|
+
ctx[EXPLICIT_DESKTOP_ROUTE_UI_LAYOUTS] = pickExplicitDesktopRouteUiLayouts(
|
|
1049
|
+
(_a = ctx.action) == null ? void 0 : _a.params.values
|
|
1050
|
+
);
|
|
1051
|
+
await next();
|
|
1052
|
+
}
|
|
1053
|
+
async function canAccessMultiPortal(ctx, multiPortalUid) {
|
|
1054
|
+
const currentRoles = getCurrentRoles(ctx);
|
|
1055
|
+
if (currentRoles.includes("root")) {
|
|
1056
|
+
return true;
|
|
1057
|
+
}
|
|
1058
|
+
if (!currentRoles.length) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
const count = await ctx.db.getRepository("rolesMultiPortals").count({
|
|
1062
|
+
filter: {
|
|
1063
|
+
roleName: currentRoles,
|
|
1064
|
+
multiPortalUid
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
return count > 0;
|
|
1068
|
+
}
|
|
1069
|
+
async function listCurrentRoleAccessibleMultiPortalUids(ctx) {
|
|
1070
|
+
const currentRoles = getCurrentRoles(ctx);
|
|
1071
|
+
if (currentRoles.includes("root")) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
if (!currentRoles.length) {
|
|
1075
|
+
return [];
|
|
1076
|
+
}
|
|
1077
|
+
const grants = await ctx.db.getRepository("rolesMultiPortals").find({
|
|
1078
|
+
fields: ["multiPortalUid"],
|
|
1079
|
+
filter: {
|
|
1080
|
+
roleName: currentRoles
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
return Array.from(
|
|
1084
|
+
new Set(
|
|
1085
|
+
grants.map((grant) => grant.get("multiPortalUid")).filter((uid) => typeof uid === "string" && !!uid)
|
|
1086
|
+
)
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
async function removeRouteIdsWithUnauthorizedAncestors(ctx, routeIds) {
|
|
1090
|
+
if (routeIds.size === 0) {
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const parentIdByRouteId = /* @__PURE__ */ new Map();
|
|
1094
|
+
let pendingRouteIds = new Set(routeIds);
|
|
1095
|
+
while (pendingRouteIds.size > 0) {
|
|
1096
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1097
|
+
fields: ["id", "parentId"],
|
|
1098
|
+
filter: {
|
|
1099
|
+
id: Array.from(pendingRouteIds)
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
pendingRouteIds = /* @__PURE__ */ new Set();
|
|
1103
|
+
for (const route of routes) {
|
|
1104
|
+
const routeId = route.get("id");
|
|
1105
|
+
if (routeId === null || routeId === void 0) {
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
const normalizedRouteId = String(routeId);
|
|
1109
|
+
const parentId = route.get("parentId");
|
|
1110
|
+
const normalizedParentId = parentId === null || parentId === void 0 ? void 0 : String(parentId);
|
|
1111
|
+
parentIdByRouteId.set(normalizedRouteId, normalizedParentId);
|
|
1112
|
+
if (normalizedParentId && !parentIdByRouteId.has(normalizedParentId)) {
|
|
1113
|
+
pendingRouteIds.add(normalizedParentId);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
for (const routeId of Array.from(routeIds)) {
|
|
1118
|
+
const visitedRouteIds = /* @__PURE__ */ new Set([routeId]);
|
|
1119
|
+
let parentId = parentIdByRouteId.get(routeId);
|
|
1120
|
+
while (parentId) {
|
|
1121
|
+
if (!routeIds.has(parentId) || visitedRouteIds.has(parentId)) {
|
|
1122
|
+
routeIds.delete(routeId);
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
visitedRouteIds.add(parentId);
|
|
1126
|
+
parentId = parentIdByRouteId.get(parentId);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
async function getMultiPortalAccessibleRouteIds(ctx, multiPortalUid) {
|
|
1131
|
+
const currentRoles = getCurrentRoles(ctx);
|
|
1132
|
+
if (currentRoles.includes("root")) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (!currentRoles.length) {
|
|
1136
|
+
return /* @__PURE__ */ new Set();
|
|
1137
|
+
}
|
|
1138
|
+
const routePermissions = await ctx.db.getRepository("rolesMultiPortalDesktopRoutes").find({
|
|
1139
|
+
fields: ["desktopRouteId"],
|
|
1140
|
+
filter: {
|
|
1141
|
+
roleName: currentRoles,
|
|
1142
|
+
multiPortalUid
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
const routeIds = /* @__PURE__ */ new Set();
|
|
1146
|
+
for (const permission of routePermissions) {
|
|
1147
|
+
const routeId = permission.get("desktopRouteId");
|
|
1148
|
+
if (routeId !== null && routeId !== void 0) {
|
|
1149
|
+
routeIds.add(String(routeId));
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
if (routeIds.size === 0) {
|
|
1153
|
+
return routeIds;
|
|
1154
|
+
}
|
|
1155
|
+
const portalRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1156
|
+
fields: ["id"],
|
|
1157
|
+
filter: {
|
|
1158
|
+
...getDesktopRoutePortalFilter(multiPortalUid),
|
|
1159
|
+
id: Array.from(routeIds)
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
const portalRouteIds = /* @__PURE__ */ new Set();
|
|
1163
|
+
for (const route of portalRoutes) {
|
|
1164
|
+
const routeId = route.get("id");
|
|
1165
|
+
if (routeId !== null && routeId !== void 0) {
|
|
1166
|
+
portalRouteIds.add(String(routeId));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
await removeRouteIdsWithUnauthorizedAncestors(ctx, portalRouteIds);
|
|
1170
|
+
return portalRouteIds;
|
|
1171
|
+
}
|
|
1172
|
+
function pickDesktopRouteRolePermissionTargetFields(route) {
|
|
1173
|
+
const result = {};
|
|
1174
|
+
for (const field of DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS) {
|
|
1175
|
+
result[field] = getRecordField(route, field) ?? null;
|
|
1176
|
+
}
|
|
1177
|
+
const children = getRecordField(route, "children");
|
|
1178
|
+
result.children = Array.isArray(children) ? children.map((child) => pickDesktopRouteRolePermissionTargetFields(child)) : [];
|
|
1179
|
+
return result;
|
|
1180
|
+
}
|
|
1181
|
+
function setDesktopRouteChildren(route, children) {
|
|
1182
|
+
if (!route || typeof route !== "object") {
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const maybeModel = route;
|
|
1186
|
+
if (typeof maybeModel.setDataValue === "function") {
|
|
1187
|
+
maybeModel.setDataValue("children", children);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
route.children = children;
|
|
1191
|
+
}
|
|
1192
|
+
function removeDesktopRoutesByIds(routes, routeIds) {
|
|
1193
|
+
return routes.map((route) => {
|
|
1194
|
+
const routeId = getRecordField(route, "id");
|
|
1195
|
+
const normalizedRouteId = routeId === null || routeId === void 0 ? void 0 : String(routeId);
|
|
1196
|
+
if (normalizedRouteId && routeIds.has(normalizedRouteId)) {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
const children = getRecordField(route, "children");
|
|
1200
|
+
if (Array.isArray(children)) {
|
|
1201
|
+
setDesktopRouteChildren(route, removeDesktopRoutesByIds(children, routeIds));
|
|
1202
|
+
}
|
|
1203
|
+
return route;
|
|
1204
|
+
}).filter((route) => route !== null);
|
|
1205
|
+
}
|
|
1206
|
+
async function getPortalOnlyDesktopRouteIds(ctx, routeIds) {
|
|
1207
|
+
if (!routeIds.length) {
|
|
1208
|
+
return /* @__PURE__ */ new Set();
|
|
1209
|
+
}
|
|
1210
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1211
|
+
fields: ["id"],
|
|
1212
|
+
appends: ["multiPortals", "uiLayouts"],
|
|
1213
|
+
filter: {
|
|
1214
|
+
id: uniqueDesktopRouteIds(routeIds)
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
const portalOnlyRouteIds = /* @__PURE__ */ new Set();
|
|
1218
|
+
for (const route of routes) {
|
|
1219
|
+
const multiPortals = route.get("multiPortals");
|
|
1220
|
+
const uiLayouts = route.get("uiLayouts");
|
|
1221
|
+
if (!Array.isArray(multiPortals) || multiPortals.length === 0) {
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
if (Array.isArray(uiLayouts) && uiLayouts.length > 0) {
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
const routeId = route.get("id");
|
|
1228
|
+
if (routeId !== null && routeId !== void 0) {
|
|
1229
|
+
portalOnlyRouteIds.add(String(routeId));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return portalOnlyRouteIds;
|
|
1233
|
+
}
|
|
1234
|
+
async function removeMultiPortalOwnedRoutesFromListResponse(ctx) {
|
|
1235
|
+
if (!Array.isArray(ctx.body)) {
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
const portalOnlyRouteIds = await getPortalOnlyDesktopRouteIds(ctx, collectDesktopRouteIds(ctx.body));
|
|
1239
|
+
if (portalOnlyRouteIds.size === 0) {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
ctx.body = removeDesktopRoutesByIds(ctx.body, portalOnlyRouteIds);
|
|
1243
|
+
}
|
|
1244
|
+
async function removeMultiPortalOwnedRouteFromGetResponse(ctx) {
|
|
1245
|
+
if (!ctx.body) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const routeIds = collectDesktopRouteIds(ctx.body);
|
|
1249
|
+
const portalOnlyRouteIds = await getPortalOnlyDesktopRouteIds(ctx, routeIds);
|
|
1250
|
+
if (portalOnlyRouteIds.size === 0) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
ctx.status = 204;
|
|
1254
|
+
ctx.body = void 0;
|
|
1255
|
+
}
|
|
1256
|
+
async function replaceListAccessibleRoutesWithPortalScopedRoutes(ctx, portalContext) {
|
|
1257
|
+
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext.portalUid);
|
|
1258
|
+
if (routeIds && routeIds.size === 0) {
|
|
1259
|
+
ctx.body = [];
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
ctx.body = await ctx.db.getRepository("desktopRoutes").find({
|
|
1263
|
+
tree: true,
|
|
1264
|
+
sort: "sort",
|
|
1265
|
+
filter: {
|
|
1266
|
+
...getDesktopRoutePortalFilter(portalContext.portalUid),
|
|
1267
|
+
...routeIds ? { id: Array.from(routeIds) } : {}
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
async function replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext) {
|
|
1272
|
+
var _a;
|
|
1273
|
+
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext.portalUid);
|
|
1274
|
+
if (routeIds && routeIds.size === 0) {
|
|
1275
|
+
ctx.status = 204;
|
|
1276
|
+
ctx.body = void 0;
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const route = await ctx.db.getRepository("desktopRoutes").findOne({
|
|
1280
|
+
sort: "sort",
|
|
1281
|
+
filterByTk: (_a = ctx.action) == null ? void 0 : _a.params.filterByTk,
|
|
1282
|
+
filter: {
|
|
1283
|
+
...getDesktopRoutePortalFilter(portalContext.portalUid),
|
|
1284
|
+
...routeIds ? { id: Array.from(routeIds) } : {}
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
if (!route) {
|
|
1288
|
+
ctx.status = 204;
|
|
1289
|
+
ctx.body = void 0;
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
ctx.status = 200;
|
|
1293
|
+
ctx.body = route;
|
|
1294
|
+
}
|
|
1295
|
+
async function prepareAccessibleDesktopRoutesForMultiPortal(ctx) {
|
|
1296
|
+
var _a;
|
|
1297
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1298
|
+
if (!portalRequest.requested) {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const portal = portalRequest.portal;
|
|
1302
|
+
if (!portal) {
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
const portalUid = portal.get("uid");
|
|
1306
|
+
const uiLayoutUid = portal.get("uiLayoutUid");
|
|
1307
|
+
if (typeof portalUid !== "string" || !portalUid || typeof uiLayoutUid !== "string" || !uiLayoutUid) {
|
|
1308
|
+
return null;
|
|
1309
|
+
}
|
|
1310
|
+
if (!await canAccessMultiPortal(ctx, portalUid)) {
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
if ((_a = ctx.action) == null ? void 0 : _a.params) {
|
|
1314
|
+
ctx.action.params.layout = uiLayoutUid;
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
portalUid,
|
|
1318
|
+
uiLayoutUid
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
async function addMultiPortalListAccessibleGuard(ctx, next) {
|
|
1322
|
+
const portalContext = await prepareAccessibleDesktopRoutesForMultiPortal(ctx);
|
|
1323
|
+
if (portalContext === null) {
|
|
1324
|
+
ctx.body = [];
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
await next();
|
|
1328
|
+
if (portalContext) {
|
|
1329
|
+
await replaceListAccessibleRoutesWithPortalScopedRoutes(ctx, portalContext);
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
await removeMultiPortalOwnedRoutesFromListResponse(ctx);
|
|
1333
|
+
}
|
|
1334
|
+
async function addMultiPortalGetAccessibleGuard(ctx, next) {
|
|
1335
|
+
const portalContext = await prepareAccessibleDesktopRoutesForMultiPortal(ctx);
|
|
1336
|
+
if (portalContext === null) {
|
|
1337
|
+
ctx.status = 204;
|
|
1338
|
+
ctx.body = void 0;
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
await next();
|
|
1342
|
+
if (portalContext) {
|
|
1343
|
+
await replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext);
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
await removeMultiPortalOwnedRouteFromGetResponse(ctx);
|
|
1347
|
+
}
|
|
1348
|
+
async function mapMultiPortalLayoutToUiLayoutForRolePermissionTargets(ctx, next) {
|
|
1349
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1350
|
+
if (portalRequest.requested && !portalRequest.portal) {
|
|
1351
|
+
ctx.status = 200;
|
|
1352
|
+
ctx.body = [];
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const portal = portalRequest.portal;
|
|
1356
|
+
const portalUid = portal == null ? void 0 : portal.get("uid");
|
|
1357
|
+
if (typeof portalUid === "string" && portalUid) {
|
|
1358
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1359
|
+
tree: true,
|
|
1360
|
+
sort: "sort",
|
|
1361
|
+
filter: getDesktopRoutePortalFilter(portalUid),
|
|
1362
|
+
fields: [...DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS]
|
|
1363
|
+
});
|
|
1364
|
+
ctx.status = 200;
|
|
1365
|
+
ctx.body = routes.map((route) => pickDesktopRouteRolePermissionTargetFields(route));
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
await next();
|
|
1369
|
+
await removeMultiPortalOwnedRoutesFromListResponse(ctx);
|
|
1370
|
+
}
|
|
1371
|
+
async function addDesktopRouteCreateMultiPortal(ctx, next) {
|
|
1372
|
+
var _a, _b;
|
|
1373
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1374
|
+
if (portalRequest.requested && !portalRequest.portal) {
|
|
1375
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const portal = portalRequest.portal;
|
|
1379
|
+
const portalUid = portal == null ? void 0 : portal.get("uid");
|
|
1380
|
+
if (typeof portalUid === "string" && portalUid) {
|
|
1381
|
+
const explicitValues = ctx[EXPLICIT_DESKTOP_ROUTE_UI_LAYOUTS];
|
|
1382
|
+
(_b = ctx.action) == null ? void 0 : _b.mergeParams(
|
|
1383
|
+
{
|
|
1384
|
+
values: withDesktopRouteMultiPortal((_a = ctx.action) == null ? void 0 : _a.params.values, portalUid, explicitValues)
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
values: "overwrite"
|
|
1388
|
+
}
|
|
1389
|
+
);
|
|
1390
|
+
await next();
|
|
1391
|
+
const desktopRouteIds = collectDesktopRouteIds(ctx.body);
|
|
1392
|
+
await removeLayoutRoutePermissionsFromPortalOnlyRoutes(ctx, desktopRouteIds);
|
|
1393
|
+
await grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, portalUid, desktopRouteIds);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
await next();
|
|
1397
|
+
}
|
|
1398
|
+
async function listEnabledMultiPortals(ctx, next) {
|
|
1399
|
+
const records = await ctx.db.getRepository("multiPortals").find({
|
|
1400
|
+
filter: withCustomMultiPortalFilter({
|
|
1401
|
+
enabled: true,
|
|
1402
|
+
"uiLayout.enabled": true
|
|
1403
|
+
}),
|
|
1404
|
+
fields: [...MULTI_PORTAL_RUNTIME_QUERY_FIELDS],
|
|
1405
|
+
appends: ["uiLayout"],
|
|
1406
|
+
sort: ["uid"]
|
|
1407
|
+
});
|
|
1408
|
+
ctx.body = records.map((record) => pickMultiPortalRuntimeFields(record));
|
|
1409
|
+
await next();
|
|
1410
|
+
}
|
|
1411
|
+
async function listAccessibleMultiPortals(ctx, next) {
|
|
1412
|
+
const accessiblePortalUids = await listCurrentRoleAccessibleMultiPortalUids(ctx);
|
|
1413
|
+
if (Array.isArray(accessiblePortalUids) && accessiblePortalUids.length === 0) {
|
|
1414
|
+
ctx.body = [];
|
|
1415
|
+
await next();
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
const filter = {
|
|
1419
|
+
enabled: true,
|
|
1420
|
+
"uiLayout.enabled": true
|
|
1421
|
+
};
|
|
1422
|
+
if (Array.isArray(accessiblePortalUids)) {
|
|
1423
|
+
filter.uid = accessiblePortalUids;
|
|
1424
|
+
}
|
|
1425
|
+
const records = await ctx.db.getRepository("multiPortals").find({
|
|
1426
|
+
filter: withCustomMultiPortalFilter(filter),
|
|
1427
|
+
fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
|
|
1428
|
+
appends: ["uiLayout"],
|
|
1429
|
+
sort: ["uid"]
|
|
1430
|
+
});
|
|
1431
|
+
ctx.body = records.map((record) => pickMultiPortalAccessibleFields(record));
|
|
1432
|
+
await next();
|
|
1433
|
+
}
|
|
1434
|
+
async function grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, multiPortalUid, desktopRouteIds) {
|
|
1435
|
+
if (!desktopRouteIds.length) {
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
if (!ctx.db.getCollection("rolesMultiPortalRoutePolicies") || !ctx.db.getCollection("rolesMultiPortalDesktopRoutes")) {
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
const routePolicies = await ctx.db.getRepository("rolesMultiPortalRoutePolicies").find({
|
|
1442
|
+
fields: ["roleName"],
|
|
1443
|
+
filter: {
|
|
1444
|
+
multiPortalUid,
|
|
1445
|
+
allowNewMenu: true
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
const roleNames = routePolicies.map((routePolicy) => routePolicy.get("roleName")).filter((roleName) => typeof roleName === "string" && !!roleName);
|
|
1449
|
+
if (!roleNames.length) {
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
const routePermissionRepository = ctx.db.getRepository("rolesMultiPortalDesktopRoutes");
|
|
1453
|
+
for (const roleName of roleNames) {
|
|
1454
|
+
for (const desktopRouteId of desktopRouteIds) {
|
|
1455
|
+
await routePermissionRepository.firstOrCreate({
|
|
1456
|
+
filterKeys: ["roleName", "multiPortalUid", "desktopRouteId"],
|
|
1457
|
+
values: {
|
|
1458
|
+
roleName,
|
|
1459
|
+
multiPortalUid,
|
|
1460
|
+
desktopRouteId
|
|
1461
|
+
},
|
|
1462
|
+
context: ctx
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
async function removeLayoutRoutePermissionsFromPortalOnlyRoutes(ctx, desktopRouteIds) {
|
|
1468
|
+
if (!desktopRouteIds.length || !ctx.db.getCollection("rolesDesktopRoutes")) {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
const portalOnlyRouteIds = await getPortalOnlyDesktopRouteIds(ctx, desktopRouteIds);
|
|
1472
|
+
if (portalOnlyRouteIds.size === 0) {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
await ctx.db.getRepository("rolesDesktopRoutes").destroy({
|
|
1476
|
+
filter: {
|
|
1477
|
+
desktopRouteId: Array.from(portalOnlyRouteIds)
|
|
1478
|
+
},
|
|
1479
|
+
context: ctx
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
1483
|
+
if (multiPortal.get("enabled") !== true) {
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
const multiPortalUid = multiPortal.get("uid");
|
|
1487
|
+
if (typeof multiPortalUid !== "string" || !multiPortalUid) {
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (isDefaultMultiPortalUid(multiPortalUid)) {
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
if (!db.getCollection("roles") || !db.getCollection("rolesMultiPortals")) {
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
const roles = await db.getRepository("roles").find({
|
|
1497
|
+
fields: ["name"],
|
|
1498
|
+
filter: {
|
|
1499
|
+
allowNewMultiPortal: true
|
|
1500
|
+
},
|
|
1501
|
+
transaction: options == null ? void 0 : options.transaction
|
|
1502
|
+
});
|
|
1503
|
+
const roleNames = roles.map((role) => role.get("name")).filter((roleName) => typeof roleName === "string" && !!roleName);
|
|
1504
|
+
const records = roleNames.map((roleName) => ({
|
|
1505
|
+
roleName,
|
|
1506
|
+
multiPortalUid
|
|
1507
|
+
}));
|
|
1508
|
+
const routePolicyRecords = roleNames.map((roleName) => ({
|
|
1509
|
+
roleName,
|
|
1510
|
+
multiPortalUid,
|
|
1511
|
+
allowNewMenu: true
|
|
1512
|
+
}));
|
|
1513
|
+
if (!records.length) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
const repository = db.getRepository("rolesMultiPortals");
|
|
1517
|
+
for (const values of records) {
|
|
1518
|
+
await repository.firstOrCreate({
|
|
1519
|
+
filterKeys: ["roleName", "multiPortalUid"],
|
|
1520
|
+
values,
|
|
1521
|
+
transaction: options == null ? void 0 : options.transaction
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
if (!db.getCollection("rolesMultiPortalRoutePolicies")) {
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
const routePolicyRepository = db.getRepository("rolesMultiPortalRoutePolicies");
|
|
1528
|
+
for (const values of routePolicyRecords) {
|
|
1529
|
+
await routePolicyRepository.firstOrCreate({
|
|
1530
|
+
filterKeys: ["roleName", "multiPortalUid"],
|
|
1531
|
+
values,
|
|
1532
|
+
transaction: options == null ? void 0 : options.transaction
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
class PluginMultiPortalServer extends import_server.Plugin {
|
|
1537
|
+
portalStorageTaskKeys = /* @__PURE__ */ new Set();
|
|
1538
|
+
portalStorageTasks = /* @__PURE__ */ new Map();
|
|
1539
|
+
async afterAdd() {
|
|
1540
|
+
}
|
|
1541
|
+
getAppName() {
|
|
1542
|
+
return this.app.name || MAIN_APP_NAME;
|
|
1543
|
+
}
|
|
1544
|
+
getMultiPortalStorageItem(multiPortal, previous = false) {
|
|
1545
|
+
const record = multiPortal;
|
|
1546
|
+
const readField = (field) => previous && typeof record.previous === "function" ? record.previous(field) : getRecordField(record, field);
|
|
1547
|
+
const portalType = readField("portalType");
|
|
1548
|
+
if (portalType !== "ai") {
|
|
1549
|
+
return null;
|
|
1550
|
+
}
|
|
1551
|
+
const portalName = normalizePortalStorageName(readField("portalName"));
|
|
1552
|
+
if (!portalName || !isValidPortalStorageName(portalName)) {
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
appName: this.getAppName(),
|
|
1557
|
+
portalName,
|
|
1558
|
+
enabled: readField("enabled") === true
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
warnPortalStorageSyncFailed(error) {
|
|
1562
|
+
var _a, _b;
|
|
1563
|
+
(_b = (_a = this.app.logger) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, "failed to sync multi-portal storage portal", {
|
|
1564
|
+
appName: this.getAppName(),
|
|
1565
|
+
error
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
getPortalStorageTaskKey(item) {
|
|
1569
|
+
return `${item.appName}/${item.portalName}`;
|
|
1570
|
+
}
|
|
1571
|
+
async runPortalStorageTask(task, options) {
|
|
1572
|
+
var _a;
|
|
1573
|
+
const run = async () => {
|
|
1574
|
+
try {
|
|
1575
|
+
await task();
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
this.warnPortalStorageSyncFailed(error);
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
if ((_a = options == null ? void 0 : options.transaction) == null ? void 0 : _a.afterCommit) {
|
|
1581
|
+
options.transaction.afterCommit(() => run());
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
await run();
|
|
1585
|
+
}
|
|
1586
|
+
async removePortalStorageDist(item) {
|
|
1587
|
+
await import_fs.default.promises.rm((0, import_utils.storagePathJoin)("portals", item.appName, item.portalName, "dist"), {
|
|
1588
|
+
force: true,
|
|
1589
|
+
recursive: true
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
waitForPortalStorageTasks() {
|
|
1593
|
+
const tasks = [...this.portalStorageTasks.values()];
|
|
1594
|
+
if (!tasks.length) {
|
|
1595
|
+
return Promise.resolve();
|
|
1596
|
+
}
|
|
1597
|
+
return Promise.allSettled(tasks).then(() => void 0);
|
|
1598
|
+
}
|
|
1599
|
+
async schedulePortalTemplateCopyAndBuild(item, template, portalDir) {
|
|
1600
|
+
var _a;
|
|
1601
|
+
const taskKey = this.getPortalStorageTaskKey(item);
|
|
1602
|
+
if (this.portalStorageTaskKeys.has(taskKey)) {
|
|
1603
|
+
await ((_a = template.cleanup) == null ? void 0 : _a.call(template));
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
this.portalStorageTaskKeys.add(taskKey);
|
|
1607
|
+
const task = (async () => {
|
|
1608
|
+
var _a2;
|
|
1609
|
+
const logPath = getPortalStorageLogPath(item);
|
|
1610
|
+
try {
|
|
1611
|
+
await appendPortalStorageLog(
|
|
1612
|
+
logPath,
|
|
1613
|
+
`Portal storage create task started for ${item.appName}/${item.portalName}.`
|
|
1614
|
+
);
|
|
1615
|
+
if (!await pathExists(portalDir)) {
|
|
1616
|
+
await appendPortalStorageLog(
|
|
1617
|
+
logPath,
|
|
1618
|
+
`Copying default portal template from ${template.dir} to ${portalDir}.`
|
|
1619
|
+
);
|
|
1620
|
+
await copyPortalTemplate(template.dir, portalDir);
|
|
1621
|
+
await appendPortalStorageLog(logPath, `Default portal template copied to ${portalDir}.`);
|
|
1622
|
+
}
|
|
1623
|
+
if (item.enabled) {
|
|
1624
|
+
await buildPortalStorageItem(portalDir, item);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
await this.removePortalStorageDist(item);
|
|
1628
|
+
await appendPortalStorageLog(logPath, `Portal dist directory removed for ${item.appName}/${item.portalName}.`);
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
await appendPortalStorageLog(
|
|
1631
|
+
logPath,
|
|
1632
|
+
`Portal storage create task failed for ${item.appName}/${item.portalName}.`
|
|
1633
|
+
);
|
|
1634
|
+
this.warnPortalStorageSyncFailed(error);
|
|
1635
|
+
} finally {
|
|
1636
|
+
await ((_a2 = template.cleanup) == null ? void 0 : _a2.call(template));
|
|
1637
|
+
this.portalStorageTaskKeys.delete(taskKey);
|
|
1638
|
+
this.portalStorageTasks.delete(taskKey);
|
|
1639
|
+
}
|
|
1640
|
+
})();
|
|
1641
|
+
this.portalStorageTasks.set(taskKey, task);
|
|
1642
|
+
await task;
|
|
1643
|
+
}
|
|
1644
|
+
async ensurePortalStorageItem(item) {
|
|
1645
|
+
const portalDir = (0, import_utils.storagePathJoin)("portals", item.appName, item.portalName);
|
|
1646
|
+
const portalIndex = import_path.default.join(portalDir, "dist", "index.html");
|
|
1647
|
+
if (!await pathExists(portalDir)) {
|
|
1648
|
+
const template = await resolvePortalTemplate(getInitPortalTemplate(), getPortalStorageLogPath(item));
|
|
1649
|
+
await this.schedulePortalTemplateCopyAndBuild(item, template, portalDir);
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
if (item.enabled) {
|
|
1653
|
+
if (!await pathExists(portalIndex)) {
|
|
1654
|
+
await buildPortalStorageItem(portalDir, item);
|
|
1655
|
+
}
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
await this.removePortalStorageDist(item);
|
|
1659
|
+
}
|
|
1660
|
+
async syncMultiPortalStorageItem(multiPortal, options, syncPrevious = false) {
|
|
1661
|
+
const currentItem = this.getMultiPortalStorageItem(multiPortal);
|
|
1662
|
+
const previousItem = syncPrevious ? this.getMultiPortalStorageItem(multiPortal, true) : null;
|
|
1663
|
+
await this.runPortalStorageTask(async () => {
|
|
1664
|
+
if (previousItem && (!currentItem || previousItem.appName !== currentItem.appName || previousItem.portalName !== currentItem.portalName || !currentItem.enabled)) {
|
|
1665
|
+
await this.removePortalStorageDist(previousItem);
|
|
1666
|
+
}
|
|
1667
|
+
if (currentItem) {
|
|
1668
|
+
await this.ensurePortalStorageItem(currentItem);
|
|
1669
|
+
}
|
|
1670
|
+
}, options);
|
|
1671
|
+
}
|
|
1672
|
+
async removeMultiPortalStorageItem(multiPortal, options) {
|
|
1673
|
+
const item = this.getMultiPortalStorageItem(multiPortal);
|
|
1674
|
+
if (!item) {
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
await this.runPortalStorageTask(async () => {
|
|
1678
|
+
const logPath = getPortalStorageLogPath(item);
|
|
1679
|
+
await appendPortalStorageLog(
|
|
1680
|
+
logPath,
|
|
1681
|
+
`Portal storage delete task started for ${item.appName}/${item.portalName}.`
|
|
1682
|
+
);
|
|
1683
|
+
await import_fs.default.promises.rm((0, import_utils.storagePathJoin)("portals", item.appName, item.portalName), { force: true, recursive: true });
|
|
1684
|
+
await appendPortalStorageLog(logPath, `Portal storage directory deleted for ${item.appName}/${item.portalName}.`);
|
|
1685
|
+
}, options);
|
|
1686
|
+
}
|
|
1687
|
+
async getMultiPortalLog(ctx, next) {
|
|
1688
|
+
const filterByTk = ctx.action.params.filterByTk;
|
|
1689
|
+
if (typeof filterByTk !== "string" || !filterByTk) {
|
|
1690
|
+
ctx.throw(400, "filterByTk is required");
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
const multiPortal = await this.db.getRepository("multiPortals").findOne({
|
|
1694
|
+
filterByTk,
|
|
1695
|
+
fields: ["uid", "portalType", "portalName", "enabled"]
|
|
1696
|
+
});
|
|
1697
|
+
const item = multiPortal ? this.getMultiPortalStorageItem(multiPortal) : null;
|
|
1698
|
+
if (!item) {
|
|
1699
|
+
ctx.throw(404, "Portal log is only available for AI portals");
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
const logPath = getPortalStorageLogPath(item);
|
|
1703
|
+
const content = await pathExists(logPath) ? await import_fs.default.promises.readFile(logPath, "utf-8") : "";
|
|
1704
|
+
ctx.body = {
|
|
1705
|
+
content,
|
|
1706
|
+
path: getPortalStorageLogRelativePath(item)
|
|
1707
|
+
};
|
|
1708
|
+
await next();
|
|
1709
|
+
}
|
|
1710
|
+
async deployPortalDist(ctx, next) {
|
|
1711
|
+
var _a, _b, _c, _d;
|
|
1712
|
+
const deployCtx = ctx;
|
|
1713
|
+
const appName = normalizePortalStorageName(((_a = deployCtx.request.body) == null ? void 0 : _a.app) || MAIN_APP_NAME) || MAIN_APP_NAME;
|
|
1714
|
+
const portalName = normalizePortalStorageName((_b = deployCtx.request.body) == null ? void 0 : _b.portal);
|
|
1715
|
+
const basePath = trimString((_c = deployCtx.request.body) == null ? void 0 : _c.basePath);
|
|
1716
|
+
const filePath = trimString((_d = deployCtx.request.file) == null ? void 0 : _d.path);
|
|
1717
|
+
try {
|
|
1718
|
+
if (!isValidPortalDeploySegment(appName)) {
|
|
1719
|
+
throw new Error("Invalid app");
|
|
1720
|
+
}
|
|
1721
|
+
if (!portalName || !isValidPortalDeploySegment(portalName)) {
|
|
1722
|
+
throw new Error("Invalid portal");
|
|
1723
|
+
}
|
|
1724
|
+
if (!basePath) {
|
|
1725
|
+
throw new Error("basePath is required");
|
|
1726
|
+
}
|
|
1727
|
+
if (!filePath || !await pathExists(filePath)) {
|
|
1728
|
+
throw new Error("file is required");
|
|
1729
|
+
}
|
|
1730
|
+
validatePortalDeployBasePath(appName, portalName, basePath);
|
|
1731
|
+
const distPath = await replacePortalDistFromArchive({
|
|
1732
|
+
filePath,
|
|
1733
|
+
appName,
|
|
1734
|
+
portalName
|
|
1735
|
+
});
|
|
1736
|
+
ctx.body = {
|
|
1737
|
+
status: "ok",
|
|
1738
|
+
app: appName,
|
|
1739
|
+
portal: portalName,
|
|
1740
|
+
basePath: resolvePortalStoragePublicPath(basePath),
|
|
1741
|
+
distPath,
|
|
1742
|
+
deployedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1743
|
+
};
|
|
1744
|
+
await next();
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
if (filePath) {
|
|
1747
|
+
await import_fs.default.promises.rm(filePath, { force: true });
|
|
1748
|
+
}
|
|
1749
|
+
ctx.throw(400, error instanceof Error ? error.message : String(error));
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async pullPortalSource(ctx, next) {
|
|
1753
|
+
var _a, _b;
|
|
1754
|
+
const appName = normalizePortalStorageName(((_a = ctx.action.params.values) == null ? void 0 : _a.app) || MAIN_APP_NAME) || MAIN_APP_NAME;
|
|
1755
|
+
const portalName = normalizePortalStorageName((_b = ctx.action.params.values) == null ? void 0 : _b.portal);
|
|
1756
|
+
try {
|
|
1757
|
+
if (!isValidPortalDeploySegment(appName)) {
|
|
1758
|
+
throw new Error("Invalid app");
|
|
1759
|
+
}
|
|
1760
|
+
if (!portalName || !isValidPortalDeploySegment(portalName)) {
|
|
1761
|
+
throw new Error("Invalid portal");
|
|
1762
|
+
}
|
|
1763
|
+
const archivePath = await packPortalSource({ appName, portalName });
|
|
1764
|
+
ctx.attachment(`${portalName}-source.tar.gz`);
|
|
1765
|
+
ctx.body = import_fs.default.createReadStream(archivePath);
|
|
1766
|
+
ctx.res.once("finish", () => {
|
|
1767
|
+
import_fs.default.promises.rm(archivePath, { force: true });
|
|
1768
|
+
});
|
|
1769
|
+
await next();
|
|
1770
|
+
} catch (error) {
|
|
1771
|
+
ctx.throw(400, error instanceof Error ? error.message : String(error));
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
async pushPortalSource(ctx, next) {
|
|
1775
|
+
var _a, _b, _c;
|
|
1776
|
+
const sourceCtx = ctx;
|
|
1777
|
+
const appName = normalizePortalStorageName(((_a = sourceCtx.request.body) == null ? void 0 : _a.app) || MAIN_APP_NAME) || MAIN_APP_NAME;
|
|
1778
|
+
const portalName = normalizePortalStorageName((_b = sourceCtx.request.body) == null ? void 0 : _b.portal);
|
|
1779
|
+
const filePath = trimString((_c = sourceCtx.request.file) == null ? void 0 : _c.path);
|
|
1780
|
+
try {
|
|
1781
|
+
if (!isValidPortalDeploySegment(appName)) {
|
|
1782
|
+
throw new Error("Invalid app");
|
|
1783
|
+
}
|
|
1784
|
+
if (!portalName || !isValidPortalDeploySegment(portalName)) {
|
|
1785
|
+
throw new Error("Invalid portal");
|
|
1786
|
+
}
|
|
1787
|
+
if (!filePath || !await pathExists(filePath)) {
|
|
1788
|
+
throw new Error("file is required");
|
|
1789
|
+
}
|
|
1790
|
+
const sourcePath = await replacePortalSourceFromArchive({
|
|
1791
|
+
filePath,
|
|
1792
|
+
appName,
|
|
1793
|
+
portalName
|
|
1794
|
+
});
|
|
1795
|
+
const sourceRevision = (/* @__PURE__ */ new Date()).toISOString();
|
|
1796
|
+
const repository = this.db.getRepository("multiPortals");
|
|
1797
|
+
const multiPortal = await repository.findOne({
|
|
1798
|
+
filterByTk: portalName,
|
|
1799
|
+
fields: ["uid", "options"]
|
|
1800
|
+
});
|
|
1801
|
+
const currentOptions = multiPortal ? getRecordField(multiPortal, "options") : void 0;
|
|
1802
|
+
const options = currentOptions && typeof currentOptions === "object" && !Array.isArray(currentOptions) ? { ...currentOptions } : {};
|
|
1803
|
+
await this.db.getRepository("multiPortals").update({
|
|
1804
|
+
filterByTk: portalName,
|
|
1805
|
+
values: {
|
|
1806
|
+
options: {
|
|
1807
|
+
...options,
|
|
1808
|
+
sourceStorage: "nocobase",
|
|
1809
|
+
sourceRevision,
|
|
1810
|
+
sourceUpdatedAt: sourceRevision
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
1814
|
+
ctx.body = {
|
|
1815
|
+
status: "ok",
|
|
1816
|
+
app: appName,
|
|
1817
|
+
portal: portalName,
|
|
1818
|
+
sourcePath,
|
|
1819
|
+
sourceRevision
|
|
1820
|
+
};
|
|
1821
|
+
await next();
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
if (filePath) {
|
|
1824
|
+
await import_fs.default.promises.rm(filePath, { force: true });
|
|
1825
|
+
}
|
|
1826
|
+
ctx.throw(400, error instanceof Error ? error.message : String(error));
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
async listAppPortalManifest(options) {
|
|
1830
|
+
const records = await this.db.getRepository("multiPortals").find({
|
|
1831
|
+
filter: withCustomMultiPortalFilter({
|
|
1832
|
+
enabled: true,
|
|
1833
|
+
"uiLayout.enabled": true
|
|
1834
|
+
}),
|
|
1835
|
+
fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
|
|
1836
|
+
appends: ["uiLayout"],
|
|
1837
|
+
sort: ["uid"],
|
|
1838
|
+
transaction: options == null ? void 0 : options.transaction
|
|
1839
|
+
});
|
|
1840
|
+
return records.map((record) => {
|
|
1841
|
+
const portal = pickMultiPortalAccessibleFields(record);
|
|
1842
|
+
const uiLayout = portal.uiLayout;
|
|
1843
|
+
const uid = typeof portal.uid === "string" ? portal.uid : "";
|
|
1844
|
+
const title = typeof portal.title === "string" ? portal.title : "";
|
|
1845
|
+
return {
|
|
1846
|
+
uid,
|
|
1847
|
+
title,
|
|
1848
|
+
icon: typeof portal.icon === "string" ? portal.icon : null,
|
|
1849
|
+
portalType: typeof portal.portalType === "string" ? portal.portalType : null,
|
|
1850
|
+
routePath: String(portal.routePath || ""),
|
|
1851
|
+
layout: typeof (uiLayout == null ? void 0 : uiLayout.layoutType) === "string" ? uiLayout.layoutType : null
|
|
1852
|
+
};
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
async getUiLayout(uid, options) {
|
|
1856
|
+
if (typeof uid !== "string" || !uid) {
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
return this.db.getRepository("uiLayouts").findOne({
|
|
1860
|
+
filterByTk: uid,
|
|
1861
|
+
transaction: options == null ? void 0 : options.transaction
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
async toAppPortalManifestItem(multiPortal, options) {
|
|
1865
|
+
const uid = getRecordField(multiPortal, "uid");
|
|
1866
|
+
const title = getRecordField(multiPortal, "title");
|
|
1867
|
+
const portalType = getRecordField(multiPortal, "portalType");
|
|
1868
|
+
const routePath = getRecordField(multiPortal, "routePath");
|
|
1869
|
+
const enabled = getRecordField(multiPortal, "enabled");
|
|
1870
|
+
if (typeof uid !== "string" || !uid || isDefaultMultiPortalUid(uid) || typeof title !== "string" || typeof routePath !== "string" || !routePath || enabled !== true) {
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
const uiLayoutUid = getRecordField(multiPortal, "uiLayoutUid");
|
|
1874
|
+
const uiLayout = getRecordField(multiPortal, "uiLayout") || await this.getUiLayout(uiLayoutUid, options);
|
|
1875
|
+
if (!uiLayout || getRecordField(uiLayout, "enabled") !== true) {
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
const icon = getRecordField(multiPortal, "icon");
|
|
1879
|
+
const layoutType = getRecordField(uiLayout, "layoutType");
|
|
1880
|
+
return {
|
|
1881
|
+
uid,
|
|
1882
|
+
title,
|
|
1883
|
+
icon: typeof icon === "string" ? icon : null,
|
|
1884
|
+
portalType: typeof portalType === "string" ? portalType : null,
|
|
1885
|
+
routePath,
|
|
1886
|
+
layout: typeof layoutType === "string" ? layoutType : null
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
async setAppManifestItem(item, options) {
|
|
1890
|
+
var _a, _b;
|
|
1891
|
+
try {
|
|
1892
|
+
await import_server.AppSupervisor.getInstance().setAppManifestItem(
|
|
1893
|
+
this.getAppName(),
|
|
1894
|
+
MULTI_PORTAL_MANIFEST_NAMESPACE,
|
|
1895
|
+
item.uid,
|
|
1896
|
+
item
|
|
1897
|
+
);
|
|
1898
|
+
await this.notifyAppManifestChanged({ action: "set", itemKey: item.uid }, options);
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
(_b = (_a = this.app.logger) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, "failed to set multi-portal app manifest item", {
|
|
1901
|
+
appName: this.getAppName(),
|
|
1902
|
+
itemKey: item.uid,
|
|
1903
|
+
error
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
async removeAppManifestItem(itemKey, options) {
|
|
1908
|
+
var _a, _b;
|
|
1909
|
+
try {
|
|
1910
|
+
await import_server.AppSupervisor.getInstance().removeAppManifestItem(
|
|
1911
|
+
this.getAppName(),
|
|
1912
|
+
MULTI_PORTAL_MANIFEST_NAMESPACE,
|
|
1913
|
+
itemKey
|
|
1914
|
+
);
|
|
1915
|
+
await this.notifyAppManifestChanged({ action: "remove", itemKey }, options);
|
|
1916
|
+
} catch (error) {
|
|
1917
|
+
(_b = (_a = this.app.logger) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, "failed to remove multi-portal app manifest item", {
|
|
1918
|
+
appName: this.getAppName(),
|
|
1919
|
+
itemKey,
|
|
1920
|
+
error
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
async publishAppManifestItem(multiPortal, options) {
|
|
1925
|
+
const uid = getRecordField(multiPortal, "uid");
|
|
1926
|
+
if (typeof uid !== "string" || !uid) {
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
const item = await this.toAppPortalManifestItem(multiPortal, options);
|
|
1930
|
+
if (item) {
|
|
1931
|
+
await this.setAppManifestItem(item, options);
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
await this.removeAppManifestItem(uid, options);
|
|
1935
|
+
}
|
|
1936
|
+
async publishUiLayoutManifestItems(uiLayout, options) {
|
|
1937
|
+
const uiLayoutUid = getRecordField(uiLayout, "uid");
|
|
1938
|
+
if (typeof uiLayoutUid !== "string" || !uiLayoutUid) {
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
const records = await this.db.getRepository("multiPortals").find({
|
|
1942
|
+
filter: withCustomMultiPortalFilter({
|
|
1943
|
+
uiLayoutUid
|
|
1944
|
+
}),
|
|
1945
|
+
fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
|
|
1946
|
+
transaction: options == null ? void 0 : options.transaction
|
|
1947
|
+
});
|
|
1948
|
+
for (const record of records) {
|
|
1949
|
+
record.set("uiLayout", uiLayout);
|
|
1950
|
+
await this.publishAppManifestItem(record, options);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
async reconcilePortalStorage(options) {
|
|
1954
|
+
const records = await this.db.getRepository("multiPortals").find({
|
|
1955
|
+
filter: {
|
|
1956
|
+
portalType: "ai"
|
|
1957
|
+
},
|
|
1958
|
+
fields: ["uid", "portalType", "portalName", "enabled"],
|
|
1959
|
+
transaction: options == null ? void 0 : options.transaction
|
|
1960
|
+
});
|
|
1961
|
+
for (const record of records) {
|
|
1962
|
+
await this.syncMultiPortalStorageItem(record, options);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
async reconcileAppManifest(options) {
|
|
1966
|
+
await this.removeAppManifest();
|
|
1967
|
+
for (const item of await this.listAppPortalManifest(options)) {
|
|
1968
|
+
await this.setAppManifestItem(item, options);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
async removeAppManifest() {
|
|
1972
|
+
var _a, _b;
|
|
1973
|
+
try {
|
|
1974
|
+
await import_server.AppSupervisor.getInstance().removeAppManifest(this.getAppName(), MULTI_PORTAL_MANIFEST_NAMESPACE);
|
|
1975
|
+
await this.notifyAppManifestChanged({ action: "clear" });
|
|
1976
|
+
} catch (error) {
|
|
1977
|
+
(_b = (_a = this.app.logger) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(_a, "failed to remove multi-portal app manifest", {
|
|
1978
|
+
appName: this.getAppName(),
|
|
1979
|
+
error
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
async notifyAppManifestChanged(message, options) {
|
|
1984
|
+
var _a;
|
|
1985
|
+
const syncMessage = {
|
|
1986
|
+
type: MULTI_PORTAL_MANIFEST_SYNC_MESSAGE_TYPE,
|
|
1987
|
+
appName: this.getAppName(),
|
|
1988
|
+
namespace: MULTI_PORTAL_MANIFEST_NAMESPACE,
|
|
1989
|
+
...message
|
|
1990
|
+
};
|
|
1991
|
+
if ((_a = options == null ? void 0 : options.transaction) == null ? void 0 : _a.afterCommit) {
|
|
1992
|
+
options.transaction.afterCommit(() => this.sendSyncMessage(syncMessage));
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
await this.sendSyncMessage(syncMessage);
|
|
1996
|
+
}
|
|
1997
|
+
async handleSyncMessage(message) {
|
|
1998
|
+
if (isRecordLike(message) && message.type === MULTI_PORTAL_MANIFEST_SYNC_MESSAGE_TYPE && message.namespace === MULTI_PORTAL_MANIFEST_NAMESPACE) {
|
|
1999
|
+
this.app.emit(MULTI_PORTAL_MANIFEST_SYNC_MESSAGE_TYPE, message);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
async beforeLoad() {
|
|
2003
|
+
this.app.resourceManager.registerPreActionHandler(
|
|
2004
|
+
"desktopRoutes:listAccessible",
|
|
2005
|
+
addMultiPortalListAccessibleGuard
|
|
2006
|
+
);
|
|
2007
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:getAccessible", addMultiPortalGetAccessibleGuard);
|
|
2008
|
+
this.app.resourceManager.registerPreActionHandler(
|
|
2009
|
+
"desktopRoutes:listRolePermissionTargets",
|
|
2010
|
+
mapMultiPortalLayoutToUiLayoutForRolePermissionTargets
|
|
2011
|
+
);
|
|
2012
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:create", captureSkipCreatePortalDirectory);
|
|
2013
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:create", normalizeMultiPortalSlugValues);
|
|
2014
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:create", preventUiLayoutRouteNameConflict);
|
|
2015
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:update", protectDefaultMultiPortalUpdate);
|
|
2016
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:update", normalizeMultiPortalSlugValues);
|
|
2017
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:update", preventUiLayoutRouteNameConflict);
|
|
2018
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", captureSkipCreatePortalDirectory);
|
|
2019
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", normalizeMultiPortalSlugValues);
|
|
2020
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", preventUiLayoutRouteNameConflict);
|
|
2021
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:create", captureExplicitDesktopRouteUiLayouts, {
|
|
2022
|
+
before: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2023
|
+
});
|
|
2024
|
+
this.app.resourceManager.registerPreActionHandler(
|
|
2025
|
+
"desktopRoutes:updateOrCreate",
|
|
2026
|
+
captureExplicitDesktopRouteUiLayouts,
|
|
2027
|
+
{
|
|
2028
|
+
before: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2029
|
+
}
|
|
2030
|
+
);
|
|
2031
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:create", addDesktopRouteCreateMultiPortal, {
|
|
2032
|
+
after: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2033
|
+
});
|
|
2034
|
+
this.app.resourceManager.registerPreActionHandler(
|
|
2035
|
+
"desktopRoutes:updateOrCreate",
|
|
2036
|
+
addDesktopRouteCreateMultiPortal,
|
|
2037
|
+
{
|
|
2038
|
+
after: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2039
|
+
}
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
async load() {
|
|
2043
|
+
this.app.acl.registerSnippet({
|
|
2044
|
+
name: "pm.multi-portal",
|
|
2045
|
+
actions: MULTI_PORTAL_MANAGEMENT_ACTIONS
|
|
2046
|
+
});
|
|
2047
|
+
this.app.acl.registerSnippet({
|
|
2048
|
+
name: "pm.acl.roles",
|
|
2049
|
+
actions: ROLE_MULTI_PORTAL_PERMISSION_ACTIONS
|
|
2050
|
+
});
|
|
2051
|
+
this.app.acl.allow("multiPortals", "listEnabled", "public");
|
|
2052
|
+
this.app.acl.allow("multiPortals", "listAccessible", "loggedIn");
|
|
2053
|
+
this.app.resourceManager.use(createPortalDeployUploadMiddleware(), {
|
|
2054
|
+
tag: "multiPortalDeployUpload",
|
|
2055
|
+
after: "acl"
|
|
2056
|
+
});
|
|
2057
|
+
this.app.resourceManager.registerActionHandler("multiPortals:listEnabled", listEnabledMultiPortals);
|
|
2058
|
+
this.app.resourceManager.registerActionHandler("multiPortals:listAccessible", listAccessibleMultiPortals);
|
|
2059
|
+
this.app.resourceManager.registerActionHandler("multiPortals:deploy", async (ctx, next) => {
|
|
2060
|
+
await this.deployPortalDist(ctx, next);
|
|
2061
|
+
});
|
|
2062
|
+
this.app.resourceManager.registerActionHandler("multiPortals:pullSource", async (ctx, next) => {
|
|
2063
|
+
await this.pullPortalSource(ctx, next);
|
|
2064
|
+
});
|
|
2065
|
+
this.app.resourceManager.registerActionHandler("multiPortals:pushSource", async (ctx, next) => {
|
|
2066
|
+
await this.pushPortalSource(ctx, next);
|
|
2067
|
+
});
|
|
2068
|
+
this.app.resourceManager.registerActionHandler("multiPortals:getLog", async (ctx, next) => {
|
|
2069
|
+
await this.getMultiPortalLog(ctx, next);
|
|
2070
|
+
});
|
|
2071
|
+
this.app.on("afterStart", async () => {
|
|
2072
|
+
await this.reconcileAppManifest();
|
|
2073
|
+
await this.reconcilePortalStorage();
|
|
2074
|
+
});
|
|
2075
|
+
this.app.on("beforeDestroy", async () => {
|
|
2076
|
+
await this.waitForPortalStorageTasks();
|
|
2077
|
+
});
|
|
2078
|
+
this.app.db.on("roles.beforeCreate", (role) => {
|
|
2079
|
+
(0, import_ensureDefaultRoleMultiPortalAccess.applyDefaultRoleMultiPortalAccess)(role);
|
|
2080
|
+
});
|
|
2081
|
+
this.app.db.on("multiPortals.afterCreate", async (multiPortal, options) => {
|
|
2082
|
+
await grantDefaultAccessToNewMultiPortal(this.app.db, multiPortal, options);
|
|
2083
|
+
await this.publishAppManifestItem(multiPortal, options);
|
|
2084
|
+
if (shouldSkipCreatePortalDirectory(options)) {
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
await this.syncMultiPortalStorageItem(multiPortal, options);
|
|
2088
|
+
});
|
|
2089
|
+
this.app.db.on("multiPortals.afterUpdate", async (multiPortal, options) => {
|
|
2090
|
+
await this.publishAppManifestItem(multiPortal, options);
|
|
2091
|
+
await this.syncMultiPortalStorageItem(multiPortal, options, true);
|
|
2092
|
+
});
|
|
2093
|
+
this.app.db.on("multiPortals.afterDestroy", async (multiPortal, options) => {
|
|
2094
|
+
const uid = getRecordField(multiPortal, "uid");
|
|
2095
|
+
if (typeof uid === "string" && uid) {
|
|
2096
|
+
await this.removeAppManifestItem(uid, options);
|
|
2097
|
+
}
|
|
2098
|
+
await this.removeMultiPortalStorageItem(multiPortal, options);
|
|
2099
|
+
});
|
|
2100
|
+
this.app.db.on("uiLayouts.afterUpdate", async (uiLayout, options) => {
|
|
2101
|
+
await this.publishUiLayoutManifestItems(uiLayout, options);
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
async install() {
|
|
2105
|
+
await ensureDefaultMultiPortals(this.db);
|
|
2106
|
+
await (0, import_ensureDefaultRoleMultiPortalAccess.ensureDefaultRoleMultiPortalAccess)(this.db);
|
|
2107
|
+
await this.reconcilePortalStorage();
|
|
2108
|
+
await this.reconcileAppManifest();
|
|
2109
|
+
}
|
|
2110
|
+
async afterEnable() {
|
|
2111
|
+
await ensureDefaultMultiPortals(this.db);
|
|
2112
|
+
await (0, import_ensureDefaultRoleMultiPortalAccess.ensureDefaultRoleMultiPortalAccess)(this.db);
|
|
2113
|
+
await this.reconcilePortalStorage();
|
|
2114
|
+
await this.reconcileAppManifest();
|
|
2115
|
+
}
|
|
2116
|
+
async afterDisable() {
|
|
2117
|
+
await this.removeAppManifest();
|
|
2118
|
+
}
|
|
2119
|
+
async remove() {
|
|
2120
|
+
await this.removeAppManifest();
|
|
2121
|
+
}
|
|
2122
|
+
static async staticImport() {
|
|
2123
|
+
await (0, import_server.appendToBuiltInPlugins)("@nocobase/plugin-multi-portal");
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
var plugin_default = PluginMultiPortalServer;
|
|
2127
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2128
|
+
0 && (module.exports = {
|
|
2129
|
+
PluginMultiPortalServer
|
|
2130
|
+
});
|