@abtnode/core 1.7.10 → 1.7.13

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/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const md5 = require('@abtnode/util/lib/md5');
4
+ const formatContext = require('@abtnode/util/lib/format-context');
4
5
  const Cron = require('@abtnode/cron');
5
6
 
6
7
  const logger = require('@abtnode/logger')('@abtnode/core');
@@ -36,7 +37,7 @@ const createEvents = require('./event');
36
37
  const pm2Events = require('./blocklet/manager/pm2-events');
37
38
  const { createStateReadyQueue, createStateReadyHandler } = require('./util/ready');
38
39
  const { getSysInfo, SysInfoEmitter } = require('./util/sysinfo');
39
- const { toStatus, fromStatus, ensureDataDirs, getQueueConcurrencyByMem } = require('./util');
40
+ const { toStatus, fromStatus, ensureDataDirs, getQueueConcurrencyByMem, getStateCrons } = require('./util');
40
41
 
41
42
  /**
42
43
  * @param {object} options
@@ -243,9 +244,9 @@ function ABTNode(options) {
243
244
  getNodeInfo: nodeAPI.getInfo.bind(nodeAPI),
244
245
  getNodeEnv: nodeAPI.getEnv.bind(nodeAPI),
245
246
  updateNodeInfo: nodeAPI.updateNodeInfo.bind(nodeAPI),
246
- addBlockletRegistry: nodeAPI.addRegistry.bind(nodeAPI),
247
- deleteBlockletRegistry: nodeAPI.deleteRegistry.bind(nodeAPI),
248
- selectBlockletRegistry: nodeAPI.selectRegistry.bind(nodeAPI),
247
+ addBlockletStore: nodeAPI.addRegistry.bind(nodeAPI),
248
+ deleteBlockletStore: nodeAPI.deleteRegistry.bind(nodeAPI),
249
+ selectBlockletStore: nodeAPI.selectRegistry.bind(nodeAPI),
249
250
  cleanupDirtyUpgradeState: states.node.cleanupDirtyUpgradeState.bind(states.node),
250
251
  addNodeOwner: states.node.addNodeOwner.bind(states.node),
251
252
  updateNodeRouting,
@@ -253,6 +254,9 @@ function ABTNode(options) {
253
254
  resetNode: (params, context) =>
254
255
  resetNode({ params, context, blockletManager, routerManager, takeRoutingSnapshot, teamManager, certManager }),
255
256
 
257
+ // Gateway
258
+ updateGateway: nodeAPI.updateGateway.bind(nodeAPI),
259
+
256
260
  // Team && Access control
257
261
 
258
262
  // Invitation
@@ -314,6 +318,10 @@ function ABTNode(options) {
314
318
  readNotifications: states.notification.read.bind(states.notification),
315
319
  unreadNotifications: states.notification.unread.bind(states.notification),
316
320
 
321
+ // AuditLog
322
+ createAuditLog: (params) => states.auditLog.create(params, instance),
323
+ getAuditLogs: states.auditLog.find.bind(states.auditLog),
324
+
317
325
  // Routing
318
326
  routerManager,
319
327
  addRoutingSite: routerManager.addRoutingSite.bind(routerManager),
@@ -339,7 +347,7 @@ function ABTNode(options) {
339
347
  handleRouting,
340
348
 
341
349
  certManager,
342
- updateNginxHttpsCert: certManager.update.bind(certManager), // TODO: 重命名:updateCert
350
+ updateCertificate: certManager.update.bind(certManager),
343
351
  getCertificates,
344
352
  addCertificate: certManager.add.bind(certManager),
345
353
  deleteCertificate: certManager.remove.bind(certManager),
@@ -397,6 +405,7 @@ function ABTNode(options) {
397
405
  ...getRoutingCrons(),
398
406
  ...blockletManager.getCrons(),
399
407
  DiskMonitor.getCron(),
408
+ ...getStateCrons(states),
400
409
  ],
401
410
  onError: (error, name) => {
402
411
  states.notification.create({
@@ -410,22 +419,45 @@ function ABTNode(options) {
410
419
  });
411
420
  };
412
421
 
422
+ const createCLILog = (action) => {
423
+ instance
424
+ .createAuditLog(
425
+ {
426
+ action,
427
+ args: {},
428
+ context: formatContext({
429
+ user: { fullName: 'CLI', role: 'self', did: options.nodeDid },
430
+ headers: { 'user-agent': 'CLI' },
431
+ }),
432
+ result: null,
433
+ },
434
+ instance
435
+ )
436
+ .catch(console.error);
437
+ };
438
+
413
439
  if (options.daemon) {
414
- certManager.start(); // 启动证书服务
440
+ // 启动证书服务
441
+ certManager
442
+ .start()
443
+ .then(() => logger.info('start certificate manager service successfully'))
444
+ .catch((error) => logger.error('start certificate manager service failed', { error }));
415
445
 
416
446
  if (process.env.NODE_ENV === 'development') {
417
447
  initCron();
418
448
  } else {
419
449
  // We should only respond to pm2 events when node is alive
420
- events.on(EVENTS.NODE_STARTED, async () => {
450
+ events.on(EVENTS.NODE_STARTED, () => {
421
451
  pm2Events.resume();
422
452
  initCron();
453
+ createCLILog('startServer');
423
454
  });
424
455
  }
425
456
  }
426
457
 
427
458
  events.on(EVENTS.NODE_STOPPED, () => {
428
459
  pm2Events.pause();
460
+ createCLILog('stopServer');
429
461
  });
430
462
 
431
463
  return Object.assign(events, {
@@ -0,0 +1,51 @@
1
+ /* eslint-disable no-continue */
2
+ /* eslint-disable no-await-in-loop */
3
+ /* eslint-disable no-underscore-dangle */
4
+
5
+ module.exports = async ({ states, printInfo }) => {
6
+ printInfo('Try to update blocklet server to 1.7.12...');
7
+
8
+ const blocklets = await states.blocklet.getBlocklets();
9
+
10
+ for (const blocklet of blocklets) {
11
+ let changed = false;
12
+
13
+ if (!blocklet.meta.bundleDid) {
14
+ blocklet.meta.bundleDid = blocklet.meta.did;
15
+ blocklet.meta.bundleName = blocklet.meta.name;
16
+ changed = true;
17
+ }
18
+
19
+ (blocklet.children || []).forEach((child) => {
20
+ if (!child.meta.bundleDid) {
21
+ child.meta.bundleDid = child.meta.did;
22
+ child.meta.bundleName = child.meta.name;
23
+ changed = true;
24
+ }
25
+ });
26
+
27
+ if (changed) {
28
+ await states.blocklet.updateBlocklet(blocklet.meta.did, { meta: blocklet.meta, children: blocklet.children });
29
+ printInfo(`Blocklet meta in blocklet.db updated: ${blocklet.meta.did}`);
30
+ }
31
+ }
32
+
33
+ const blockletExtras = await states.blockletExtras.find({});
34
+
35
+ for (const extra of blockletExtras) {
36
+ let changed = false;
37
+ const children = await states.blockletExtras.getSettings(extra.did, 'children', []);
38
+ (children || []).forEach((child) => {
39
+ if (!child.meta.bundleDid) {
40
+ child.meta.bundleDid = child.meta.did;
41
+ child.meta.bundleName = child.meta.name;
42
+ changed = true;
43
+ }
44
+ });
45
+
46
+ if (changed) {
47
+ await states.blockletExtras.setSettings(extra.did, { children });
48
+ printInfo(`Blocklet dynamic component meta in blocklet_extra.db updated: ${extra.did}`);
49
+ }
50
+ }
51
+ };
@@ -8,6 +8,7 @@ const {
8
8
  DEFAULT_IP_DOMAIN,
9
9
  BLOCKLET_PROXY_PATH_PREFIX,
10
10
  BLOCKLET_SITE_GROUP_SUFFIX,
11
+ GATEWAY_REQ_LIMIT,
11
12
  } = require('@abtnode/constant');
12
13
  const { BLOCKLET_UI_INTERFACES } = require('@blocklet/meta/lib/constants');
13
14
  const logger = require('@abtnode/logger')('@abtnode/core:router');
@@ -107,12 +108,18 @@ class Router {
107
108
  logger.info('updateRoutingTable routingTable:', { routingTable: this.routingTable });
108
109
  logger.info('updateRoutingTable certificates:', { certificates: certificates.map((item) => item.domain) });
109
110
 
111
+ const requestLimit = nodeInfo.routing.requestLimit || { enable: false, rate: GATEWAY_REQ_LIMIT.min };
112
+ if (requestLimit.enabled) {
113
+ requestLimit.maxInstantRate = requestLimit.rate >= 20 ? 20 : requestLimit.rate + 20;
114
+ }
115
+
110
116
  await this.provider.update({
111
117
  routingTable: this.routingTable,
112
118
  certificates,
113
119
  commonHeaders: headers,
114
120
  services,
115
121
  nodeInfo: pick(nodeInfo, ['name', 'version', 'port', 'mode', 'enableWelcomePage', 'routing']),
122
+ requestLimit,
116
123
  });
117
124
  }
118
125
 
@@ -1,3 +1,33 @@
1
1
  # State DB
2
2
 
3
- All ABT Node states are managed by files in this folder.
3
+ All Blocklet Server states are managed by files in this folder.
4
+
5
+ ## blocklet
6
+
7
+ - `meta` meta of bundle _defined in @blocklet/meta/schema_
8
+ - `did`, `name`: component id, default from source meta, can be changed before install
9
+ - `title`, `description`: component info, default from source meta, can be changed before install
10
+ - `bundleDdid`, `bundleName`: bundle id, copy from source meta
11
+ - `version`: component version
12
+ - `price`: used for charging
13
+ - `logo`: component logo
14
+ - `logoUrl`: component logo (from store)
15
+ - `interfaces`: for resolving: runtime port; service config; web base prefix; api base prefix
16
+ - `environments`: component environment
17
+ - `scripts`: for hook of component lifecycle
18
+ - `engine`, `timeout`, `group`, `main`: related to process startup
19
+ - `dist`: bundle source
20
+ - `source`: type of bundle source
21
+ - `deployedFrom`: information of bundle source
22
+ - `sourceUrl`: url of bundle source
23
+ - `appDid`: app instance id _app only_
24
+ - `status`, `startedAt`, `installedAt`, `stoppedAt` app status
25
+ - `deletedAt` _component only_
26
+ - `mode` app mode
27
+ - `ports` runtime port resolved from meta
28
+ - `<ENV_NAME>`: `<port number>`
29
+ - `environments[]`: env variables generated by server, e.g. BLOCKLET_APP_SK, BLOCKLET_APP_DID
30
+ - `<key>`: `<value>`
31
+ - `children`: component of app, same structure as parent, can be nested
32
+ - `mountPoint` mount path of web site. _component only_
33
+ - `dynamic` is dynamically added. _component only_
@@ -0,0 +1,382 @@
1
+ /* eslint-disable no-async-promise-executor */
2
+ const pick = require('lodash/pick');
3
+ const get = require('lodash/get');
4
+ const { getDisplayName } = require('@blocklet/meta/lib/util');
5
+ const { BLOCKLET_SITE_GROUP_SUFFIX } = require('@abtnode/constant');
6
+ const logger = require('@abtnode/logger')('@abtnode/core:states:audit-log');
7
+
8
+ const BaseState = require('./base');
9
+
10
+ const { parse } = require('../util/ua');
11
+ const { lookup } = require('../util/ip');
12
+
13
+ const getServerInfo = (info) => `[${info.name}](${info.routing.adminPath}/settings/about)`;
14
+ const getBlockletInfo = (blocklet, info) => `[${getDisplayName(blocklet)} v${blocklet.meta.version}](${info.routing.adminPath}/blocklets/${blocklet.meta.did})`; // prettier-ignore
15
+ const expandTeam = async (teamDid, info, node) => {
16
+ if (!teamDid) {
17
+ return '';
18
+ }
19
+
20
+ if (teamDid === info.did) {
21
+ return getServerInfo(info);
22
+ }
23
+
24
+ const { blocklet } = node.states;
25
+ const doc = await blocklet.getBlocklet(teamDid);
26
+ return doc ? getBlockletInfo(doc, info) : '';
27
+ };
28
+ const expandSite = async (siteId, info, node) => {
29
+ if (!siteId) {
30
+ return '';
31
+ }
32
+
33
+ const { blocklet, site } = node.states;
34
+ const doc = await site.findOne({ _id: siteId });
35
+ if (!doc) {
36
+ return '';
37
+ }
38
+
39
+ if (doc.domain === '*') {
40
+ return 'default site';
41
+ }
42
+
43
+ if (doc.domain === '') {
44
+ return getServerInfo(info);
45
+ }
46
+
47
+ if (doc.domain.endsWith(BLOCKLET_SITE_GROUP_SUFFIX)) {
48
+ const tmp = await blocklet.getBlocklet(doc.domain.replace(BLOCKLET_SITE_GROUP_SUFFIX, ''));
49
+ return tmp ? getBlockletInfo(tmp, info) : '';
50
+ }
51
+
52
+ return '';
53
+ };
54
+ const expandUser = async (teamDid, userDid, passportId, info, node) => {
55
+ if (!teamDid || !userDid) {
56
+ return ['', ''];
57
+ }
58
+
59
+ const user = await node.getUser({ teamDid, user: { did: userDid } });
60
+ if (!user) {
61
+ return ['', ''];
62
+ }
63
+
64
+ const passport = user.passports.find((x) => x.id === passportId);
65
+
66
+ if (teamDid === info.did) {
67
+ return [`[${user.fullName}](${info.routing.adminPath}/team/members)`, passport ? passport.name : ''];
68
+ }
69
+
70
+ return [`[${user.fullName}](${info.routing.adminPath}/blocklets/${teamDid}/members)`, passport ? passport.name : ''];
71
+ };
72
+
73
+ /**
74
+ * Create log content in markdown format
75
+ *
76
+ * @param {string} action - GraphQL query/mutation name
77
+ * @param {object} args - GraphQL arguments
78
+ * @param {object} context - request context: user, ip, user-agent, etc.
79
+ * @param {object} result - GraphQL resolve result
80
+ * @param {object} info - server info
81
+ * @param {object} node - server instance
82
+ * @return {string} the generated markdown source
83
+ */
84
+ const getLogContent = async (action, args, context, result, info, node) => {
85
+ const [team, site, [user, passport]] = await Promise.all([
86
+ expandTeam(args.teamDid, info, node),
87
+ expandSite(args.id, info, node),
88
+ expandUser(args.teamDid, args.userDid || get(args, 'user.did') || args.ownerDid, args.passportId, info, node),
89
+ ]);
90
+
91
+ switch (action) {
92
+ // blocklets
93
+ case 'installBlocklet':
94
+ return `installed blocklet ${getBlockletInfo(result, info)} from ${result.deployedFrom}`;
95
+ case 'startBlocklet':
96
+ return `started blocklet ${getBlockletInfo(result, info)}`;
97
+ case 'restartBlocklet':
98
+ return `restarted blocklet ${getBlockletInfo(result, info)}`;
99
+ case 'reloadBlocklet':
100
+ return `reloaded blocklet ${getBlockletInfo(result, info)}`;
101
+ case 'stopBlocklet':
102
+ return `stopped blocklet ${getBlockletInfo(result, info)}`;
103
+ case 'resetBlocklet':
104
+ return `reset blocklet ${getBlockletInfo(result, info)} data and config`;
105
+ case 'deleteBlocklet':
106
+ return `removed blocklet ${getBlockletInfo(result, info)} ${args.keepData ? 'but kept its data and config' : 'and its data and config'}`; // prettier-ignore
107
+ case 'installComponent':
108
+ return `added component from ${args.file ? 'Upload' : args.url} at **${args.mountPoint}** to ${getBlockletInfo(result, info)}`; // prettier-ignore
109
+ case 'deleteComponent':
110
+ return `removed component ${args.did} from blocklet ${getBlockletInfo(result, info)}`;
111
+ case 'configBlocklet':
112
+ return `updated following ${args.childDid ? `child ${args.childDid}` : ''} config for blocklet ${getBlockletInfo(result, info)}:\n${args.configs.map(x => `- ${x.key}: ${x.value}\n`)}`; // prettier-ignore
113
+ case 'upgradeBlocklet':
114
+ return `upgraded blocklet ${getBlockletInfo(result, info)} to v${result.meta.version}`;
115
+ case 'updateChildBlocklets':
116
+ return `upgraded components for blocklet ${getBlockletInfo(result, info)}`;
117
+
118
+ // store
119
+ case 'addBlockletStore':
120
+ return `added blocklet store ${args.url}`;
121
+ case 'deleteBlockletStore':
122
+ return `removed blocklet store ${args.url}`;
123
+ case 'selectBlockletStore':
124
+ return `selected blocklet store ${args.url}`;
125
+
126
+ // teams: members/passports
127
+ case 'addUser':
128
+ if (args.passport) {
129
+ return `${args.reason} and received **${args.passport.name}** passport from ${team}`;
130
+ }
131
+ return `joined team ${team} by ${args.reason}`;
132
+ case 'updateUser':
133
+ return `${args.reason} and received **${args.passport.name}** passport from ${team}`;
134
+ case 'configWhoCanAccess':
135
+ return `updated access control policy to **${args.value}** for ${team} when ${args.reason}`;
136
+ case 'configPassportIssuance':
137
+ return `${args.enabled ? 'enabled' : 'disabled'} passport issuance for ${team}`;
138
+ case 'createPassportIssuance':
139
+ return `issued **${args.name}** passport to ${user} for ${team}, issuance id: ${result.id}`;
140
+ case 'processPassportIssuance':
141
+ return `claimed passport **${args.name}** from ${team}, issuance id: ${args.sessionId}`;
142
+ case 'revokeUserPassport':
143
+ return `revoked **${passport}** passport of user ${user} for ${team}`;
144
+ case 'enableUserPassport':
145
+ return `enabled **${passport}** passport of user ${user} for ${team}`;
146
+ case 'updateUserApproval':
147
+ return `${args.user.approved ? 'enabled' : 'disabled'} user ${user} for ${team}`;
148
+ case 'deletePassportIssuance':
149
+ return `removed passport issuance ${args.sessionId} from ${team}`;
150
+ case 'createInvitation':
151
+ return `created member invitation(${result.inviteId}: ${args.remark}) with **${args.role}** passport for ${team}`; // prettier-ignore
152
+ case 'deleteInvitation':
153
+ return `removed unused member invitation(${args.inviteId}) from ${team}`;
154
+ case 'createRole':
155
+ return `created passport ${args.name}(${args.title}) for ${team}`;
156
+ case 'updateRole':
157
+ return `updated passport ${args.role.name}(${args.role.title}) for ${team}`;
158
+ case 'updatePermissionsForRole':
159
+ return `granted following permissions to passport ${args.roleName} for ${team}: \n${args.grantNames.map(x => `- ${x}`).join('\n')}`; // prettier-ignore
160
+ case 'configTrustedPassports':
161
+ if (args.trustedPassports.length === 0) {
162
+ return `removed all trusted passport issuers for ${team}`;
163
+ }
164
+ return `updated trusted passport issuers to following for ${team}: \n${args.trustedPassports.map(x => `- ${x.remark}: ${x.issuerDid}`).join('\n')}`; // prettier-ignore
165
+
166
+ // accessKeys
167
+ case 'createAccessKey':
168
+ return `created access key ${result.accessKeyId} with passport ${args.passport}: ${args.remark}`;
169
+ case 'updateAccessKey':
170
+ return `updated access key ${result.accessKeyId} with following changes:\n - passport: ${args.passport}\n - remark: ${args.remark}`; // prettier-ignore
171
+ case 'deleteAccessKey':
172
+ return `deleted access key ${args.accessKeyId}`; // prettier-ignore
173
+
174
+ // integrations
175
+ case 'createWebHook':
176
+ return `added integration ${result._id}: \n- type: ${args.type}\n${args.params.map(x => `- ${x.name}: ${x.value}`).join('\n')}`; // prettier-ignore
177
+ case 'deleteWebHook':
178
+ return `deleted integration ${args.id}`;
179
+
180
+ // server
181
+ case 'updateNodeInfo':
182
+ return `updated basic server settings: \n${Object.keys(args).map(x => `- ${x}: ${args[x]}`).join('\n')}`; // prettier-ignore
183
+ case 'upgradeNodeVersion':
184
+ return `triggered server upgrade to ${info.nextVersion}`;
185
+ case 'startServer':
186
+ return 'server was started';
187
+ case 'stopServer':
188
+ return 'server was stopped';
189
+
190
+ // certificates
191
+ case 'addCertificate':
192
+ return `added certificate #${args.id}, domain ${result.domain}`;
193
+ case 'deleteCertificate':
194
+ return `deleted certificate #${args.id}`;
195
+ case 'updateCertificate':
196
+ return `updated certificate #${args.id}`;
197
+ case 'issueLetsEncryptCert':
198
+ return `tried to issue lets encrypt certificate for domain **${args.domain}**, ticket: ${result._id}`;
199
+
200
+ // router
201
+ case 'addDomainAlias':
202
+ return `added extra domain **${args.domainAlias}** to ${site}`; // prettier-ignore
203
+ case 'deleteDomainAlias':
204
+ return `removed extra domain **${args.domainAlias}** from ${site}`; // prettier-ignore
205
+ case 'updateGateway': {
206
+ let message = args.requestLimit.enabled ? `status: enabled, rate: ${args.requestLimit.rate}` : 'status: disabled';
207
+ message = `update gateway. ${message}`;
208
+
209
+ return message;
210
+ }
211
+
212
+ default:
213
+ return action;
214
+ }
215
+ };
216
+
217
+ const getLogCategory = (action) => {
218
+ switch (action) {
219
+ // blocklets
220
+ case 'installBlocklet':
221
+ case 'installComponent':
222
+ case 'startBlocklet':
223
+ case 'restartBlocklet':
224
+ case 'reloadBlocklet':
225
+ case 'stopBlocklet':
226
+ case 'resetBlocklet':
227
+ case 'deleteBlocklet':
228
+ case 'deleteComponent':
229
+ case 'configBlocklet':
230
+ case 'upgradeBlocklet':
231
+ case 'updateChildBlocklets':
232
+ return 'blocklet';
233
+
234
+ // store
235
+ case 'addBlockletStore':
236
+ case 'deleteBlockletStore':
237
+ case 'selectBlockletStore':
238
+ return 'server';
239
+
240
+ // teams: members/passports
241
+ case 'addUser':
242
+ case 'updateUser':
243
+ case 'configWhoCanAccess':
244
+ case 'processPassportIssuance':
245
+ case 'configPassportIssuance':
246
+ case 'createPassportIssuance':
247
+ case 'deletePassportIssuance':
248
+ case 'revokeUserPassport':
249
+ case 'enableUserPassport':
250
+ case 'updateUserApproval':
251
+ case 'createInvitation':
252
+ case 'deleteInvitation':
253
+ case 'createRole':
254
+ case 'updateRole':
255
+ case 'updatePermissionsForRole':
256
+ case 'configTrustedPassports':
257
+ return 'team';
258
+
259
+ // accessKeys
260
+ case 'createAccessKey':
261
+ case 'updateAccessKey':
262
+ case 'deleteAccessKey':
263
+ return 'security';
264
+
265
+ // integrations
266
+ case 'createWebHook':
267
+ case 'deleteWebHook':
268
+ return 'integrations';
269
+
270
+ // server
271
+ case 'updateNodeInfo':
272
+ case 'upgradeNodeVersion':
273
+ case 'startServer':
274
+ case 'stopServer':
275
+ return 'server';
276
+
277
+ // certificates
278
+ case 'addCertificate':
279
+ case 'deleteCertificate':
280
+ case 'updateCertificate':
281
+ case 'issueLetsEncryptCert':
282
+ return 'certificates';
283
+
284
+ case 'updateGateway':
285
+ return 'gateway';
286
+
287
+ default:
288
+ return '';
289
+ }
290
+ };
291
+
292
+ class AuditLogState extends BaseState {
293
+ constructor(baseDir, options = {}) {
294
+ super(baseDir, { filename: 'audit-log.db', ...options });
295
+ }
296
+
297
+ /**
298
+ * Create new audit log
299
+ *
300
+ * @param {string} action the api name
301
+ * @param {object} args the api args
302
+ * @param {object} result the api result
303
+ * @param {object} context the log context
304
+ * {
305
+ protocol: 'http',
306
+ user: {
307
+ meta: 'profile',
308
+ fullName: 'wangshijun',
309
+ email: 'shijun@arcblock.io',
310
+ type: 'profile',
311
+ did: 'z1jq5bGF64wnZiy29EbRyuQqUxmRHmSdt1Q',
312
+ pk: 'zHTcKwgi9DK8US8QQY9K7wQ3qF79CtrWYn6BYAgTQUeVQ',
313
+ passports: [ [Object] ],
314
+ approved: true,
315
+ locale: 'en',
316
+ firstLoginAt: '2022-04-27T22:55:51.788Z',
317
+ lastLoginAt: '2022-04-27T22:55:51.788Z',
318
+ _id: 'dvOIJJVUJHGxnWBU',
319
+ createdAt: '2022-04-27T22:55:51.789Z',
320
+ updatedAt: '2022-04-27T22:55:51.789Z',
321
+ role: 'owner',
322
+ passportId: 'z2iTzSDhwGQFtYXeya8kqCyXro1tRkwUuwN6K'
323
+ },
324
+ url: '/api/gql?locale=en',
325
+ query: { locale: 'en' },
326
+ hostname: '192.168.123.236',
327
+ port: 0,
328
+ ip: '127.0.0.1'
329
+ }
330
+ * @return {object} new doc
331
+ */
332
+ create({ action, args, context, result }, node) {
333
+ return new Promise(async (resolve) => {
334
+ // Do not store secure configs in audit log
335
+ if (Array.isArray(args.configs)) {
336
+ args.configs.forEach((x) => {
337
+ if (x.secure) {
338
+ x.value = '******';
339
+ }
340
+ });
341
+ }
342
+
343
+ try {
344
+ const { ip, ua, user } = context;
345
+ const [info, geoInfo, uaInfo] = await Promise.all([node.states.node.read(), lookup(ip), parse(ua)]);
346
+
347
+ const data = await this.asyncDB.insert({
348
+ scope: args.teamDid || info.did, // server or blocklet did
349
+ action,
350
+ category: await getLogCategory(action, args, context, result, info, node),
351
+ content: (await getLogContent(action, args, context, result, info, node)).trim(),
352
+ actor: pick(user.actual || user, ['did', 'fullName', 'role']),
353
+ extra: args,
354
+ env: pick(uaInfo, ['browser', 'os', 'device']),
355
+ geo: pick(geoInfo || { country: '', city: '' }, ['country', 'city']),
356
+ ip,
357
+ ua,
358
+ });
359
+
360
+ logger.info('create', data);
361
+ return resolve(data);
362
+ } catch (err) {
363
+ logger.error('create error', { error: err, action, args, context });
364
+ return resolve(null);
365
+ }
366
+ });
367
+ }
368
+
369
+ async find({ scope, category, paging } = {}) {
370
+ const conditions = {};
371
+ if (scope) {
372
+ conditions.scope = scope;
373
+ }
374
+ if (category) {
375
+ conditions.category = category;
376
+ }
377
+
378
+ return this.paginate(conditions, { createdAt: -1 }, { pageSize: 20, ...paging });
379
+ }
380
+ }
381
+
382
+ module.exports = AuditLogState;
@@ -9,7 +9,7 @@ const detectPort = require('detect-port');
9
9
  const Lock = require('@abtnode/util/lib/lock');
10
10
  const security = require('@abtnode/util/lib/security');
11
11
  const { fixPerson, fixInterfaces } = require('@blocklet/meta/lib/fix');
12
- const { getDisplayName } = require('@blocklet/meta/lib/util');
12
+ const { getDisplayName, forEachBlocklet } = require('@blocklet/meta/lib/util');
13
13
  const {
14
14
  BlockletStatus,
15
15
  BlockletSource,
@@ -21,7 +21,7 @@ const {
21
21
  const logger = require('@abtnode/logger')('state-blocklet');
22
22
 
23
23
  const BaseState = require('./base');
24
- const { forEachBlocklet, checkDuplicateComponents } = require('../util/blocklet');
24
+ const { checkDuplicateComponents, ensureMeta } = require('../util/blocklet');
25
25
  const { validateBlockletMeta } = require('../util');
26
26
 
27
27
  const lock = new Lock('blocklet-port-assign-lock');
@@ -143,7 +143,6 @@ class BlockletState extends BaseState {
143
143
  }
144
144
 
145
145
  addBlocklet({
146
- did,
147
146
  meta,
148
147
  source = BlockletSource.registry,
149
148
  status = BlockletStatus.added,
@@ -151,7 +150,7 @@ class BlockletState extends BaseState {
151
150
  mode = BLOCKLET_MODES.PRODUCTION,
152
151
  children: rawChildren = [],
153
152
  } = {}) {
154
- return this.getBlocklet(did).then(
153
+ return this.getBlocklet(meta.did).then(
155
154
  (doc) =>
156
155
  new Promise(async (resolve, reject) => {
157
156
  if (doc) {
@@ -162,7 +161,10 @@ class BlockletState extends BaseState {
162
161
  try {
163
162
  fixPerson(meta);
164
163
  fixInterfaces(meta);
165
- const sanitized = validateBlockletMeta(meta);
164
+ let sanitized = validateBlockletMeta(meta);
165
+ // bundle info
166
+ sanitized = ensureMeta(sanitized);
167
+ sanitized = omit(sanitized, ['htmlAst']);
166
168
 
167
169
  // get ports
168
170
  await lock.acquire();
@@ -180,7 +182,7 @@ class BlockletState extends BaseState {
180
182
  {
181
183
  appDid: null, // will updated later when updating blocklet environments
182
184
  mode,
183
- meta: omit(sanitized, ['htmlAst']),
185
+ meta: sanitized,
184
186
  status,
185
187
  source,
186
188
  deployedFrom,
@@ -530,7 +532,7 @@ class BlockletState extends BaseState {
530
532
  throw new Error('Cannot add self as a component');
531
533
  }
532
534
 
533
- checkDuplicateComponents([child], newChildren);
535
+ checkDuplicateComponents([child, ...newChildren]);
534
536
 
535
537
  newChildren.push({
536
538
  mountPoint,