@abtnode/core 1.16.8-beta-0c0c5eb2 → 1.16.8-beta-b3039c78
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/lib/api/node.js +22 -4
- package/lib/blocklet/manager/disk.js +161 -120
- package/lib/blocklet/storage/backup/spaces.js +2 -4
- package/lib/blocklet/storage/restore/spaces.js +1 -1
- package/lib/event.js +43 -0
- package/lib/index.js +2 -0
- package/lib/states/audit-log.js +24 -2
- package/lib/util/spaces.js +18 -5
- package/package.json +20 -19
package/lib/api/node.js
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
/* eslint-disable no-underscore-dangle */
|
|
3
3
|
const assert = require('assert');
|
|
4
4
|
const os = require('os');
|
|
5
|
+
const LRU = require('lru-cache');
|
|
5
6
|
const isDocker = require('@abtnode/util/lib/is-docker');
|
|
6
7
|
const isGitpod = require('@abtnode/util/lib/is-gitpod');
|
|
7
8
|
const getFolderSize = require('@abtnode/util/lib/get-folder-size');
|
|
8
9
|
const canPackageReadWrite = require('@abtnode/util/lib/can-pkg-rw');
|
|
9
10
|
const { toDelegateAddress } = require('@arcblock/did-util');
|
|
11
|
+
const { SERVER_CACHE_TTL } = require('@abtnode/constant');
|
|
10
12
|
|
|
11
13
|
const logger = require('@abtnode/logger')('@abtnode/core:api:node');
|
|
12
14
|
|
|
@@ -36,6 +38,11 @@ class NodeAPI {
|
|
|
36
38
|
historyLength: MONITOR_HISTORY_LENGTH,
|
|
37
39
|
});
|
|
38
40
|
|
|
41
|
+
this.cache = new LRU({
|
|
42
|
+
max: 1,
|
|
43
|
+
maxAge: SERVER_CACHE_TTL,
|
|
44
|
+
});
|
|
45
|
+
|
|
39
46
|
this.state = state;
|
|
40
47
|
}
|
|
41
48
|
|
|
@@ -59,15 +66,26 @@ class NodeAPI {
|
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
async getInfo() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
async getInfo({ useCache } = {}) {
|
|
70
|
+
let info;
|
|
71
|
+
if (useCache && this.cache.has('info')) {
|
|
72
|
+
info = this.cache.get('info');
|
|
73
|
+
} else {
|
|
74
|
+
info = await this.state.read();
|
|
75
|
+
const env = await this.state.getEnvironments();
|
|
76
|
+
info.environments = Object.keys(env).map((x) => ({ key: x, value: env[x] }));
|
|
77
|
+
this.cache.set('info', info);
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
info.uptime = process.uptime() * 1000;
|
|
67
81
|
|
|
68
82
|
return info;
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
deleteCache() {
|
|
86
|
+
this.cache.del('del');
|
|
87
|
+
}
|
|
88
|
+
|
|
71
89
|
async getDiskInfo() {
|
|
72
90
|
let diskInfo = { app: 0, cache: 0, log: 0, data: 0, blocklets: 0 };
|
|
73
91
|
try {
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const flat = require('flat');
|
|
6
|
+
const LRU = require('lru-cache');
|
|
6
7
|
const get = require('lodash/get');
|
|
7
8
|
const omit = require('lodash/omit');
|
|
8
9
|
const merge = require('lodash/merge');
|
|
9
10
|
const pick = require('lodash/pick');
|
|
10
11
|
const isEmpty = require('lodash/isEmpty');
|
|
11
|
-
const cloneDeep = require('lodash/cloneDeep');
|
|
12
12
|
const { isNFTExpired, getNftExpirationDate } = require('@abtnode/util/lib/nft');
|
|
13
13
|
const didDocument = require('@abtnode/util/lib/did-document');
|
|
14
14
|
const { sign } = require('@arcblock/jwt');
|
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
BLOCKLET_INSTALL_TYPE,
|
|
23
23
|
NODE_MODES,
|
|
24
24
|
APP_STRUCT_VERSION,
|
|
25
|
+
BLOCKLET_CACHE_TTL,
|
|
25
26
|
} = require('@abtnode/constant');
|
|
26
27
|
|
|
27
28
|
const getBlockletEngine = require('@blocklet/meta/lib/engine');
|
|
@@ -41,6 +42,7 @@ const {
|
|
|
41
42
|
const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
|
|
42
43
|
const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
|
|
43
44
|
const { titleSchema, updateMountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
|
|
45
|
+
const { emailConfigSchema } = require('@blocklet/sdk/lib/validators/email');
|
|
44
46
|
const Lock = require('@abtnode/util/lib/lock');
|
|
45
47
|
|
|
46
48
|
const {
|
|
@@ -82,7 +84,6 @@ const {
|
|
|
82
84
|
checkBlockletProcessHealthy,
|
|
83
85
|
validateBlocklet,
|
|
84
86
|
validateBlockletChainInfo,
|
|
85
|
-
statusMap,
|
|
86
87
|
pruneBlockletBundle,
|
|
87
88
|
getDiskInfo,
|
|
88
89
|
getRuntimeEnvironments,
|
|
@@ -128,7 +129,7 @@ const UpgradeComponents = require('./helper/upgrade-components');
|
|
|
128
129
|
const BlockletDownloader = require('../downloader/blocklet-downloader');
|
|
129
130
|
const RollbackCache = require('./helper/rollback-cache');
|
|
130
131
|
const { migrateApplicationToStructV2 } = require('./helper/migrate-application-to-struct-v2');
|
|
131
|
-
const { getBackupFilesUrlFromEndpoint,
|
|
132
|
+
const { getBackupFilesUrlFromEndpoint, getBackupEndpoint } = require('../../util/spaces');
|
|
132
133
|
const { validateAddSpaceGateway, validateUpdateSpaceGateway } = require('../../validators/space-gateway');
|
|
133
134
|
|
|
134
135
|
const { formatEnvironments, shouldUpdateBlockletStatus, getBlockletMeta, validateOwner } = util;
|
|
@@ -221,7 +222,10 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
221
222
|
this.teamManager = teamManager;
|
|
222
223
|
|
|
223
224
|
// cached installed blocklets for performance
|
|
224
|
-
this.cachedBlocklets =
|
|
225
|
+
this.cachedBlocklets = new LRU({
|
|
226
|
+
max: 100,
|
|
227
|
+
maxAge: BLOCKLET_CACHE_TTL,
|
|
228
|
+
});
|
|
225
229
|
|
|
226
230
|
this.runtimeMonitor = new BlockletRuntimeMonitor({ historyLength: MONITOR_HISTORY_LENGTH, states });
|
|
227
231
|
|
|
@@ -944,7 +948,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
944
948
|
}
|
|
945
949
|
|
|
946
950
|
// Get blocklet by blockletDid or appDid
|
|
947
|
-
async detail({ did, attachConfig = true, attachRuntimeInfo
|
|
951
|
+
async detail({ did, attachConfig = true, attachRuntimeInfo, useCache }, context) {
|
|
948
952
|
if (!did) {
|
|
949
953
|
throw new Error('did should not be empty');
|
|
950
954
|
}
|
|
@@ -953,49 +957,36 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
953
957
|
return states.blocklet.getBlocklet(did);
|
|
954
958
|
}
|
|
955
959
|
|
|
956
|
-
if (
|
|
957
|
-
|
|
958
|
-
const blocklet = await this.getBlocklet(did, { throwOnNotExist: false });
|
|
959
|
-
return blocklet;
|
|
960
|
-
} catch (e) {
|
|
961
|
-
logger.error('get blocklet detail error', { error: e });
|
|
962
|
-
return states.blocklet.getBlocklet(did);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
const nodeInfo = await states.node.read();
|
|
960
|
+
if (attachRuntimeInfo) {
|
|
961
|
+
const nodeInfo = await states.node.read();
|
|
967
962
|
|
|
968
|
-
|
|
969
|
-
|
|
963
|
+
return this._attachRuntimeInfo({ did, nodeInfo, diskInfo: true, context });
|
|
964
|
+
}
|
|
970
965
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
await Promise.all(
|
|
975
|
-
blocklets.map((x) => {
|
|
976
|
-
if (isBeforeInstalled(x.status)) {
|
|
977
|
-
return x;
|
|
978
|
-
}
|
|
966
|
+
if (useCache && this.cachedBlocklets.has(did)) {
|
|
967
|
+
return this.cachedBlocklets.get(did);
|
|
968
|
+
}
|
|
979
969
|
|
|
980
|
-
|
|
981
|
-
|
|
970
|
+
try {
|
|
971
|
+
const blocklet = await this.getBlocklet(did, { throwOnNotExist: false });
|
|
982
972
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
)
|
|
992
|
-
).filter(Boolean);
|
|
973
|
+
if (blocklet) {
|
|
974
|
+
if (blocklet.appDid) {
|
|
975
|
+
this.cachedBlocklets.set(blocklet.appDid, blocklet);
|
|
976
|
+
}
|
|
977
|
+
if (blocklet.appPid) {
|
|
978
|
+
this.cachedBlocklets.set(blocklet.appPid, blocklet);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
993
981
|
|
|
994
|
-
|
|
995
|
-
|
|
982
|
+
return blocklet;
|
|
983
|
+
} catch (e) {
|
|
984
|
+
logger.error('get blocklet detail error', { error: e });
|
|
985
|
+
return states.blocklet.getBlocklet(did);
|
|
986
|
+
}
|
|
996
987
|
}
|
|
997
988
|
|
|
998
|
-
async list({ includeRuntimeInfo = true,
|
|
989
|
+
async list({ includeRuntimeInfo = true, query, filter } = {}, context) {
|
|
999
990
|
const condition = { ...flat(query || {}) };
|
|
1000
991
|
if (filter === 'external-only') {
|
|
1001
992
|
condition.controller = {
|
|
@@ -1012,7 +1003,7 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1012
1003
|
const blocklets = await states.blocklet.getBlocklets(condition);
|
|
1013
1004
|
|
|
1014
1005
|
if (includeRuntimeInfo) {
|
|
1015
|
-
return this.
|
|
1006
|
+
return this._attachBlockletListRuntimeInfo({ blocklets }, context);
|
|
1016
1007
|
}
|
|
1017
1008
|
|
|
1018
1009
|
return blocklets;
|
|
@@ -1235,6 +1226,33 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1235
1226
|
return newState;
|
|
1236
1227
|
}
|
|
1237
1228
|
|
|
1229
|
+
async configNotification({ did, notification = {} }) {
|
|
1230
|
+
let newConfig = {};
|
|
1231
|
+
try {
|
|
1232
|
+
newConfig = JSON.parse(notification);
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
logger.error('parse configNotification error', { error });
|
|
1235
|
+
throw new Error('parse configNotification error');
|
|
1236
|
+
}
|
|
1237
|
+
const enabled = newConfig?.email?.enabled;
|
|
1238
|
+
if (enabled) {
|
|
1239
|
+
const { error } = emailConfigSchema.validate(
|
|
1240
|
+
pick(newConfig?.email || {}, ['from', 'host', 'port', 'user', 'password'])
|
|
1241
|
+
);
|
|
1242
|
+
if (error) {
|
|
1243
|
+
logger.error('configNotification validate error', { error });
|
|
1244
|
+
throw new Error(error.message);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
const oldConfig = await states.blockletExtras.getSettings(did, 'notification', {});
|
|
1248
|
+
// NOTICE:validate 本身没有做任何字段规范化处理,所以处理后的结果跟处理前是一样的,最终结果的合并先使用原始值
|
|
1249
|
+
const mergeConfig = { ...oldConfig, ...newConfig };
|
|
1250
|
+
await states.blockletExtras.setSettings(did, { notification: mergeConfig });
|
|
1251
|
+
const newState = await this.getBlocklet(did);
|
|
1252
|
+
this.emit(BlockletEvents.updated, newState);
|
|
1253
|
+
return newState;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1238
1256
|
// TODO: this method can be removed if title is not changed anymore
|
|
1239
1257
|
async updateComponentTitle({ did, rootDid: inputRootDid, title }) {
|
|
1240
1258
|
await titleSchema.validateAsync(title);
|
|
@@ -1596,6 +1614,14 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1596
1614
|
return states.backup.getBlockletBackups({ did });
|
|
1597
1615
|
}
|
|
1598
1616
|
|
|
1617
|
+
deleteCache(did) {
|
|
1618
|
+
const cache = this.cachedBlocklets.get(did);
|
|
1619
|
+
if (cache) {
|
|
1620
|
+
this.cachedBlocklets.del(cache.appDid);
|
|
1621
|
+
this.cachedBlocklets.del(cache.appPid);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1599
1625
|
// ============================================================================================
|
|
1600
1626
|
// Private API that are used by self of helper function
|
|
1601
1627
|
// ============================================================================================
|
|
@@ -1891,11 +1917,18 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1891
1917
|
await spacesBackup.backup();
|
|
1892
1918
|
|
|
1893
1919
|
await states.backup.success(backup._id, {
|
|
1894
|
-
targetUrl: getBackupFilesUrlFromEndpoint(
|
|
1920
|
+
targetUrl: getBackupFilesUrlFromEndpoint(getBackupEndpoint(blocklet?.environments)),
|
|
1895
1921
|
});
|
|
1896
1922
|
|
|
1897
1923
|
// 备份成功了
|
|
1898
|
-
this.emit(BlockletEvents.backupProgress, {
|
|
1924
|
+
this.emit(BlockletEvents.backupProgress, {
|
|
1925
|
+
appDid,
|
|
1926
|
+
meta: { did: appPid },
|
|
1927
|
+
completed: true,
|
|
1928
|
+
progress: 100,
|
|
1929
|
+
context,
|
|
1930
|
+
blocklet,
|
|
1931
|
+
});
|
|
1899
1932
|
} catch (error) {
|
|
1900
1933
|
await states.backup.fail(backup._id, {
|
|
1901
1934
|
message: error?.message,
|
|
@@ -1906,6 +1939,8 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1906
1939
|
completed: true,
|
|
1907
1940
|
progress: -1,
|
|
1908
1941
|
message: error?.message,
|
|
1942
|
+
context,
|
|
1943
|
+
blocklet,
|
|
1909
1944
|
});
|
|
1910
1945
|
throw error;
|
|
1911
1946
|
}
|
|
@@ -1922,59 +1957,69 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
1922
1957
|
*/
|
|
1923
1958
|
// eslint-disable-next-line no-unused-vars
|
|
1924
1959
|
async _onRestoreFromSpaces({ input, context }) {
|
|
1925
|
-
if (input.delay) {
|
|
1926
|
-
await sleep(input.delay);
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
1960
|
const appPid = input.appDid;
|
|
1930
1961
|
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1962
|
+
try {
|
|
1963
|
+
if (input.delay) {
|
|
1964
|
+
await sleep(input.delay);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
this.emit(BlockletEvents.restoreProgress, {
|
|
1968
|
+
appDid: input.appDid,
|
|
1969
|
+
meta: { did: appPid },
|
|
1970
|
+
status: RESTORE_PROGRESS_STATUS.start,
|
|
1971
|
+
});
|
|
1936
1972
|
|
|
1937
|
-
|
|
1973
|
+
const userDid = context.user.did;
|
|
1974
|
+
const spacesRestore = new SpacesRestore({ ...input, appPid, event: this, userDid, referrer: context.referrer });
|
|
1975
|
+
const params = await spacesRestore.restore();
|
|
1938
1976
|
|
|
1939
|
-
|
|
1940
|
-
|
|
1977
|
+
const removeRestoreDir = () => {
|
|
1978
|
+
if (fs.existsSync(spacesRestore.restoreDir)) {
|
|
1979
|
+
fs.remove(spacesRestore.restoreDir).catch((err) => {
|
|
1980
|
+
logger.error('failed to remove restore dir', { error: err, dir: spacesRestore.restoreDir });
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1941
1984
|
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1985
|
+
this.emit(BlockletEvents.restoreProgress, {
|
|
1986
|
+
appDid: input.appDid,
|
|
1987
|
+
meta: { did: appPid },
|
|
1988
|
+
status: RESTORE_PROGRESS_STATUS.installing,
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
try {
|
|
1992
|
+
await installApplicationFromBackup({
|
|
1993
|
+
url: `file://${spacesRestore.restoreDir}`,
|
|
1994
|
+
moveDir: true,
|
|
1995
|
+
...merge(...params),
|
|
1996
|
+
manager: this,
|
|
1997
|
+
states,
|
|
1998
|
+
controller: input.controller,
|
|
1999
|
+
context: { ...context, startImmediately: true },
|
|
1946
2000
|
});
|
|
1947
|
-
}
|
|
1948
|
-
};
|
|
1949
2001
|
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2002
|
+
removeRestoreDir();
|
|
2003
|
+
} catch (error) {
|
|
2004
|
+
removeRestoreDir();
|
|
2005
|
+
throw error;
|
|
2006
|
+
}
|
|
1955
2007
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
...merge(...params),
|
|
1961
|
-
manager: this,
|
|
1962
|
-
states,
|
|
1963
|
-
controller: input.controller,
|
|
1964
|
-
context: { ...context, startImmediately: true },
|
|
2008
|
+
this.emit(BlockletEvents.restoreProgress, {
|
|
2009
|
+
appDid: input.appDid,
|
|
2010
|
+
meta: { did: appPid },
|
|
2011
|
+
status: RESTORE_PROGRESS_STATUS.completed,
|
|
1965
2012
|
});
|
|
1966
|
-
|
|
1967
|
-
removeRestoreDir();
|
|
1968
2013
|
} catch (error) {
|
|
1969
|
-
|
|
2014
|
+
this.emit(BlockletEvents.restoreProgress, {
|
|
2015
|
+
appDid: input.appDid,
|
|
2016
|
+
meta: { did: appPid },
|
|
2017
|
+
status: RESTORE_PROGRESS_STATUS.error,
|
|
2018
|
+
message: error.message,
|
|
2019
|
+
});
|
|
2020
|
+
|
|
1970
2021
|
throw error;
|
|
1971
2022
|
}
|
|
1972
|
-
|
|
1973
|
-
this.emit(BlockletEvents.restoreProgress, {
|
|
1974
|
-
appDid: input.appDid,
|
|
1975
|
-
meta: { did: appPid },
|
|
1976
|
-
status: RESTORE_PROGRESS_STATUS.completed,
|
|
1977
|
-
});
|
|
1978
2023
|
}
|
|
1979
2024
|
|
|
1980
2025
|
async _updateBlockletEnvironment(did) {
|
|
@@ -2027,7 +2072,27 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2027
2072
|
return states.blocklet.updateBlocklet(did, blocklet);
|
|
2028
2073
|
}
|
|
2029
2074
|
|
|
2030
|
-
async
|
|
2075
|
+
async _attachBlockletListRuntimeInfo({ blocklets }, context) {
|
|
2076
|
+
const nodeInfo = await states.node.read();
|
|
2077
|
+
return (
|
|
2078
|
+
await Promise.all(
|
|
2079
|
+
blocklets.map((x) => {
|
|
2080
|
+
if (isBeforeInstalled(x.status)) {
|
|
2081
|
+
return x;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
return this._attachRuntimeInfo({
|
|
2085
|
+
did: x.meta.did,
|
|
2086
|
+
nodeInfo,
|
|
2087
|
+
diskInfo: false,
|
|
2088
|
+
context,
|
|
2089
|
+
});
|
|
2090
|
+
})
|
|
2091
|
+
)
|
|
2092
|
+
).filter(Boolean);
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
async _attachRuntimeInfo({ did, nodeInfo, diskInfo = true, context }) {
|
|
2031
2096
|
if (!did) {
|
|
2032
2097
|
throw new Error('did should not be empty');
|
|
2033
2098
|
}
|
|
@@ -2039,24 +2104,6 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2039
2104
|
return null;
|
|
2040
2105
|
}
|
|
2041
2106
|
|
|
2042
|
-
const fromCache = !!cachedBlocklet;
|
|
2043
|
-
|
|
2044
|
-
// if from cached data, only use cache data of runtime info (engine, diskInfo, runtimeInfo...)
|
|
2045
|
-
if (fromCache) {
|
|
2046
|
-
const cached = {};
|
|
2047
|
-
forEachBlockletSync(cachedBlocklet, (component, { id }) => {
|
|
2048
|
-
cached[id] = component;
|
|
2049
|
-
});
|
|
2050
|
-
|
|
2051
|
-
Object.assign(blocklet, pick(cachedBlocklet, ['appRuntimeInfo', 'diskInfo']));
|
|
2052
|
-
|
|
2053
|
-
forEachBlockletSync(blocklet, (component, { id }) => {
|
|
2054
|
-
if (cached[id]) {
|
|
2055
|
-
Object.assign(component, pick(cached[id], ['runtimeInfo']));
|
|
2056
|
-
}
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
2107
|
// 处理 domainAliases#value SLOT_FOR_IP_DNS_SITE
|
|
2061
2108
|
if (blocklet?.site?.domainAliases?.length) {
|
|
2062
2109
|
const nodeIp = await getAccessibleExternalNodeIp(nodeInfo);
|
|
@@ -2069,24 +2116,18 @@ class BlockletManager extends BaseBlockletManager {
|
|
|
2069
2116
|
// app runtime info, app status
|
|
2070
2117
|
blocklet.appRuntimeInfo = this.runtimeMonitor.getRuntimeInfo(blocklet.meta.did);
|
|
2071
2118
|
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
component.engine = getEngine(getBlockletEngineNameByPlatform(component.meta)).describe();
|
|
2076
|
-
|
|
2077
|
-
if (level === 0) {
|
|
2078
|
-
component.diskInfo = await getDiskInfo(component, {
|
|
2079
|
-
useFakeDiskInfo: !diskInfo,
|
|
2080
|
-
});
|
|
2081
|
-
}
|
|
2119
|
+
// app disk info, component runtime info, component engine
|
|
2120
|
+
await forEachBlocklet(blocklet, async (component, { level }) => {
|
|
2121
|
+
component.engine = getEngine(getBlockletEngineNameByPlatform(component.meta)).describe();
|
|
2082
2122
|
|
|
2083
|
-
|
|
2123
|
+
if (level === 0) {
|
|
2124
|
+
component.diskInfo = await getDiskInfo(component, {
|
|
2125
|
+
useFakeDiskInfo: !diskInfo,
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2084
2128
|
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
}
|
|
2088
|
-
});
|
|
2089
|
-
}
|
|
2129
|
+
component.runtimeInfo = this.runtimeMonitor.getRuntimeInfo(blocklet.meta.did, component.env.id);
|
|
2130
|
+
});
|
|
2090
2131
|
|
|
2091
2132
|
return blocklet;
|
|
2092
2133
|
} catch (err) {
|
|
@@ -218,11 +218,9 @@ class SpacesBackup extends BaseBackup {
|
|
|
218
218
|
const { locale } = this.input;
|
|
219
219
|
// @FIXME: get locale @jianchao
|
|
220
220
|
if (statusCode === 403) {
|
|
221
|
-
throw new Error(
|
|
222
|
-
`${translate(locale, 'backup.space.error.title')}: ${translate(locale, 'backup.space.error.forbidden')}`
|
|
223
|
-
);
|
|
221
|
+
throw new Error(translate(locale, 'backup.space.error.forbidden'));
|
|
224
222
|
}
|
|
225
|
-
throw new Error(
|
|
223
|
+
throw new Error(message);
|
|
226
224
|
}
|
|
227
225
|
}
|
|
228
226
|
|
package/lib/event.js
CHANGED
|
@@ -12,6 +12,7 @@ const eventHub =
|
|
|
12
12
|
process.env.NODE_ENV === 'test' ? require('@arcblock/event-hub/single') : require('@arcblock/event-hub');
|
|
13
13
|
|
|
14
14
|
const states = require('./states');
|
|
15
|
+
const { getBackupEndpoint, getBackupFilesUrlFromEndpoint, getDIDSpacesUrlFromEndpoint } = require('./util/spaces');
|
|
15
16
|
|
|
16
17
|
const routingSnapshotPrefix = (blocklet) => (blocklet.mode === BLOCKLET_MODES.DEVELOPMENT ? '[DEV] ' : '');
|
|
17
18
|
|
|
@@ -25,6 +26,7 @@ module.exports = ({
|
|
|
25
26
|
handleRouting,
|
|
26
27
|
domainStatus,
|
|
27
28
|
teamAPI,
|
|
29
|
+
nodeAPI,
|
|
28
30
|
teamManager,
|
|
29
31
|
certManager,
|
|
30
32
|
routerManager,
|
|
@@ -53,12 +55,34 @@ module.exports = ({
|
|
|
53
55
|
teamManager.deleteTeam(data?.meta?.did, { closeDatabase: false });
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
// clear cache
|
|
59
|
+
if (
|
|
60
|
+
[
|
|
61
|
+
BlockletEvents.updated,
|
|
62
|
+
BlockletEvents.started,
|
|
63
|
+
BlockletEvents.removed,
|
|
64
|
+
BlockletEvents.statusChange,
|
|
65
|
+
BlockletEvents.installed,
|
|
66
|
+
].includes(name)
|
|
67
|
+
) {
|
|
68
|
+
const did = get(data, 'meta.did');
|
|
69
|
+
if (did) {
|
|
70
|
+
logger.info('delete blocklet cache on update', { did });
|
|
71
|
+
blockletManager.deleteCache(did);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
56
75
|
if (typeof eventHandler === 'function') {
|
|
57
76
|
eventHandler({ name, data });
|
|
58
77
|
}
|
|
59
78
|
});
|
|
60
79
|
});
|
|
61
80
|
|
|
81
|
+
eventHub.on(EVENTS.NODE_UPDATED, () => {
|
|
82
|
+
logger.info('node update');
|
|
83
|
+
nodeAPI.deleteCache();
|
|
84
|
+
});
|
|
85
|
+
|
|
62
86
|
// Wipe sensitive data
|
|
63
87
|
// Emit events to event hub
|
|
64
88
|
// Emit events to node listener
|
|
@@ -259,6 +283,25 @@ module.exports = ({
|
|
|
259
283
|
logger.error('Reload gateway failed on blocklet.connectedSpace', { error: err });
|
|
260
284
|
});
|
|
261
285
|
logger.info('Reload gateway after blocklet connected to space', { event: eventName, did: blocklet.appDid });
|
|
286
|
+
} else if (BlockletEvents.backupProgress === eventName && payload?.completed) {
|
|
287
|
+
try {
|
|
288
|
+
const backupEndpoint = getBackupEndpoint(blocklet?.environments);
|
|
289
|
+
|
|
290
|
+
await node.createAuditLog({
|
|
291
|
+
action: 'backupToSpaces',
|
|
292
|
+
args: {
|
|
293
|
+
did: blocklet.meta.did,
|
|
294
|
+
url: getDIDSpacesUrlFromEndpoint(backupEndpoint),
|
|
295
|
+
backupUrl: getBackupFilesUrlFromEndpoint(backupEndpoint),
|
|
296
|
+
success: payload?.progress === 100,
|
|
297
|
+
errorMessage: payload?.message,
|
|
298
|
+
},
|
|
299
|
+
context: payload?.context ?? {},
|
|
300
|
+
result: cloneDeep(blocklet),
|
|
301
|
+
});
|
|
302
|
+
} catch (error) {
|
|
303
|
+
logger.error('Failed to createAuditLog for backupToSpaces failed', { error });
|
|
304
|
+
}
|
|
262
305
|
}
|
|
263
306
|
|
|
264
307
|
if (
|
package/lib/index.js
CHANGED
|
@@ -254,6 +254,7 @@ function ABTNode(options) {
|
|
|
254
254
|
configPublicToStore: blockletManager.configPublicToStore.bind(blockletManager),
|
|
255
255
|
configNavigations: blockletManager.configNavigations.bind(blockletManager),
|
|
256
256
|
configOAuth: blockletManager.configOAuth.bind(blockletManager),
|
|
257
|
+
configNotification: blockletManager.configNotification.bind(blockletManager),
|
|
257
258
|
updateWhoCanAccess: blockletManager.updateWhoCanAccess.bind(blockletManager),
|
|
258
259
|
updateComponentTitle: blockletManager.updateComponentTitle.bind(blockletManager),
|
|
259
260
|
updateComponentMountPoint: blockletManager.updateComponentMountPoint.bind(blockletManager),
|
|
@@ -471,6 +472,7 @@ function ABTNode(options) {
|
|
|
471
472
|
handleRouting,
|
|
472
473
|
domainStatus,
|
|
473
474
|
teamAPI,
|
|
475
|
+
nodeAPI,
|
|
474
476
|
teamManager,
|
|
475
477
|
certManager,
|
|
476
478
|
routerManager,
|
package/lib/states/audit-log.js
CHANGED
|
@@ -12,6 +12,14 @@ const BaseState = require('./base');
|
|
|
12
12
|
const { parse } = require('../util/ua');
|
|
13
13
|
|
|
14
14
|
const getServerInfo = (info) => `[${info.name}](${joinUrl(info.routing.adminPath, '/settings/about')})`;
|
|
15
|
+
/**
|
|
16
|
+
* @description
|
|
17
|
+
* @param {import('@abtnode/client').BlockletState} blocklet
|
|
18
|
+
* @param {{
|
|
19
|
+
* routing: { adminPath: string }
|
|
20
|
+
* }} info
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
15
23
|
const getBlockletInfo = (blocklet, info) => `[${getDisplayName(blocklet)} v${blocklet.meta.version}](${joinUrl(info.routing.adminPath, '/blocklets/', blocklet.meta.did, '/overview')})`; // prettier-ignore
|
|
16
24
|
const expandTeam = async (teamDid, info, node) => {
|
|
17
25
|
if (!teamDid) {
|
|
@@ -81,7 +89,7 @@ const expandUser = async (teamDid, userDid, passportId, info, node) => {
|
|
|
81
89
|
* Create log content in markdown format
|
|
82
90
|
*
|
|
83
91
|
* @param {string} action - GraphQL query/mutation name
|
|
84
|
-
* @param {
|
|
92
|
+
* @param {Record<string, string>} args - GraphQL arguments
|
|
85
93
|
* @param {object} context - request context: user, ip, user-agent, etc.
|
|
86
94
|
* @param {object} result - GraphQL resolve result
|
|
87
95
|
* @param {object} info - server info
|
|
@@ -116,12 +124,22 @@ const getLogContent = async (action, args, context, result, info, node) => {
|
|
|
116
124
|
case 'deleteComponent':
|
|
117
125
|
return `removed component ${args.did} from blocklet ${getBlockletInfo(result, info)}`;
|
|
118
126
|
case 'configBlocklet':
|
|
119
|
-
return `updated following config for blocklet ${getBlockletInfo(result, info)}:\n${args.configs.map(x =>
|
|
127
|
+
return `updated following config for blocklet ${getBlockletInfo(result, info)}:\n${args.configs.map(x => `* ${x.key}: ${x.value}`).join('\n')}`; // prettier-ignore
|
|
120
128
|
case 'upgradeBlocklet':
|
|
121
129
|
if (result.resultStatus === 'failed') {
|
|
122
130
|
return `upgrade blocklet failed: ${getBlockletInfo(result, info)}`;
|
|
123
131
|
}
|
|
124
132
|
return `upgraded blocklet ${getBlockletInfo(result, info)} to v${result.meta.version}`;
|
|
133
|
+
case 'backupToSpaces':
|
|
134
|
+
if (args?.success) {
|
|
135
|
+
return `Backup ${getBlockletInfo(result, info)} to ${
|
|
136
|
+
args.url
|
|
137
|
+
} successfully:\n- Backup files have been stored [here](${args.backupUrl})`;
|
|
138
|
+
}
|
|
139
|
+
return `Backup ${getBlockletInfo(result, info)} to ${
|
|
140
|
+
args.url
|
|
141
|
+
} failed:\n- The reason for the error is: <span style='color:red'>${args.errorMessage}</span>`;
|
|
142
|
+
|
|
125
143
|
case 'upgradeComponents':
|
|
126
144
|
return `upgraded components for blocklet ${getBlockletInfo(result, info)}`;
|
|
127
145
|
case 'configPublicToStore':
|
|
@@ -136,6 +154,8 @@ const getLogContent = async (action, args, context, result, info, node) => {
|
|
|
136
154
|
)}`;
|
|
137
155
|
case 'configOAuth':
|
|
138
156
|
return `updated following OAuth for blocklet ${getBlockletInfo(result, info)}:\n${args.oauth}`;
|
|
157
|
+
case 'configNotification':
|
|
158
|
+
return `updated following notification setting for blocklet ${getBlockletInfo(result, info)}`;
|
|
139
159
|
case 'updateComponentTitle':
|
|
140
160
|
return `update component title to **${args.title}** for blocklet ${getBlockletInfo(result, info)}`;
|
|
141
161
|
case 'updateComponentMountPoint':
|
|
@@ -295,8 +315,10 @@ const getLogCategory = (action) => {
|
|
|
295
315
|
case 'configPublicToStore':
|
|
296
316
|
case 'configNavigations':
|
|
297
317
|
case 'configOAuth':
|
|
318
|
+
case 'configNotification':
|
|
298
319
|
case 'updateComponentTitle':
|
|
299
320
|
case 'updateComponentMountPoint':
|
|
321
|
+
case 'backupToSpaces':
|
|
300
322
|
return 'blocklet';
|
|
301
323
|
|
|
302
324
|
// store,此处应该返回 server
|
package/lib/util/spaces.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
2
|
+
const isUrl = require('is-url');
|
|
3
|
+
const isArray = require('lodash/isArray');
|
|
2
4
|
const isEmpty = require('lodash/isEmpty');
|
|
3
5
|
const joinUrl = require('url-join');
|
|
4
6
|
|
|
@@ -7,10 +9,12 @@ const joinUrl = require('url-join');
|
|
|
7
9
|
* @param {import('@abtnode/client').ConfigEntry[]} configs
|
|
8
10
|
* @return {string | null}
|
|
9
11
|
*/
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const getBackupEndpoint = (configs) => {
|
|
13
|
+
if (!isArray(configs) || isEmpty(configs)) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return configs.find((config) => config.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_BACKUP_ENDPOINT)?.value || null;
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
/**
|
|
@@ -33,7 +37,16 @@ function getBackupFilesUrlFromEndpoint(endpoint) {
|
|
|
33
37
|
return joinUrl(prefix, 'space', spaceDid, 'apps', appDid, 'explorer', `?key=/apps/${appDid}/.did-objects/${appDid}/`);
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
function getDIDSpacesUrlFromEndpoint(endpoint) {
|
|
41
|
+
if (!isUrl(endpoint)) {
|
|
42
|
+
throw new Error(`Endpoint(${endpoint}) is not a valid url`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return endpoint.replace(/\/api\/space\/.+/, '');
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
module.exports = {
|
|
37
|
-
|
|
49
|
+
getBackupEndpoint,
|
|
38
50
|
getBackupFilesUrlFromEndpoint,
|
|
51
|
+
getDIDSpacesUrlFromEndpoint,
|
|
39
52
|
};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.16.8-beta-
|
|
6
|
+
"version": "1.16.8-beta-b3039c78",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -19,18 +19,18 @@
|
|
|
19
19
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@abtnode/auth": "1.16.8-beta-
|
|
23
|
-
"@abtnode/certificate-manager": "1.16.8-beta-
|
|
24
|
-
"@abtnode/constant": "1.16.8-beta-
|
|
25
|
-
"@abtnode/cron": "1.16.8-beta-
|
|
26
|
-
"@abtnode/db": "1.16.8-beta-
|
|
27
|
-
"@abtnode/logger": "1.16.8-beta-
|
|
28
|
-
"@abtnode/queue": "1.16.8-beta-
|
|
29
|
-
"@abtnode/rbac": "1.16.8-beta-
|
|
30
|
-
"@abtnode/router-provider": "1.16.8-beta-
|
|
31
|
-
"@abtnode/static-server": "1.16.8-beta-
|
|
32
|
-
"@abtnode/timemachine": "1.16.8-beta-
|
|
33
|
-
"@abtnode/util": "1.16.8-beta-
|
|
22
|
+
"@abtnode/auth": "1.16.8-beta-b3039c78",
|
|
23
|
+
"@abtnode/certificate-manager": "1.16.8-beta-b3039c78",
|
|
24
|
+
"@abtnode/constant": "1.16.8-beta-b3039c78",
|
|
25
|
+
"@abtnode/cron": "1.16.8-beta-b3039c78",
|
|
26
|
+
"@abtnode/db": "1.16.8-beta-b3039c78",
|
|
27
|
+
"@abtnode/logger": "1.16.8-beta-b3039c78",
|
|
28
|
+
"@abtnode/queue": "1.16.8-beta-b3039c78",
|
|
29
|
+
"@abtnode/rbac": "1.16.8-beta-b3039c78",
|
|
30
|
+
"@abtnode/router-provider": "1.16.8-beta-b3039c78",
|
|
31
|
+
"@abtnode/static-server": "1.16.8-beta-b3039c78",
|
|
32
|
+
"@abtnode/timemachine": "1.16.8-beta-b3039c78",
|
|
33
|
+
"@abtnode/util": "1.16.8-beta-b3039c78",
|
|
34
34
|
"@arcblock/did": "1.18.78",
|
|
35
35
|
"@arcblock/did-auth": "1.18.78",
|
|
36
36
|
"@arcblock/did-ext": "^1.18.78",
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"@arcblock/event-hub": "1.18.78",
|
|
40
40
|
"@arcblock/jwt": "^1.18.78",
|
|
41
41
|
"@arcblock/pm2-events": "^0.0.5",
|
|
42
|
-
"@arcblock/validator": "^1.18.
|
|
42
|
+
"@arcblock/validator": "^1.18.78",
|
|
43
43
|
"@arcblock/vc": "1.18.78",
|
|
44
|
-
"@blocklet/constant": "1.16.8-beta-
|
|
45
|
-
"@blocklet/meta": "1.16.8-beta-
|
|
46
|
-
"@blocklet/sdk": "1.16.8-beta-
|
|
47
|
-
"@did-space/client": "^0.2.
|
|
44
|
+
"@blocklet/constant": "1.16.8-beta-b3039c78",
|
|
45
|
+
"@blocklet/meta": "1.16.8-beta-b3039c78",
|
|
46
|
+
"@blocklet/sdk": "1.16.8-beta-b3039c78",
|
|
47
|
+
"@did-space/client": "^0.2.91",
|
|
48
48
|
"@fidm/x509": "^1.2.1",
|
|
49
49
|
"@ocap/mcrypto": "1.18.78",
|
|
50
50
|
"@ocap/util": "1.18.78",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"js-yaml": "^4.1.0",
|
|
72
72
|
"kill-port": "^2.0.1",
|
|
73
73
|
"lodash": "^4.17.21",
|
|
74
|
+
"lru-cache": "^6.0.0",
|
|
74
75
|
"moment-timezone": "^0.5.37",
|
|
75
76
|
"node-stream-zip": "^1.15.0",
|
|
76
77
|
"p-limit": "^3.1.0",
|
|
@@ -94,5 +95,5 @@
|
|
|
94
95
|
"express": "^4.18.2",
|
|
95
96
|
"jest": "^27.5.1"
|
|
96
97
|
},
|
|
97
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "3f27acff94c144a1cf3dcf14bc1972c79a330641"
|
|
98
99
|
}
|