@abtnode/core 1.17.5-beta-20251211-104355-426d7eb6 → 1.17.5-beta-20251214-231110-497f8d27
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/team.js +71 -3
- package/lib/blocklet/manager/disk.js +3 -28
- package/lib/blocklet/manager/helper/blue-green-get-componentids.js +11 -16
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +29 -23
- package/lib/blocklet/migration-dist/migration.cjs +1 -1
- package/lib/states/audit-log.js +34 -9
- package/lib/states/blocklet-child.js +119 -45
- package/lib/states/blocklet-extras.js +63 -1
- package/lib/states/blocklet.js +39 -11
- package/lib/states/notification.js +4 -2
- package/lib/util/blocklet.js +2 -0
- package/lib/util/migration-sqlite-to-postgres.js +162 -1
- package/package.json +39 -39
- package/lib/blocklet/manager/helper/blue-green-update-blocklet-status.js +0 -18
package/lib/api/team.js
CHANGED
|
@@ -10,6 +10,7 @@ const { joinURL, withoutTrailingSlash } = require('ufo');
|
|
|
10
10
|
const { Op } = require('sequelize');
|
|
11
11
|
const dayjs = require('@abtnode/util/lib/dayjs');
|
|
12
12
|
const logger = require('@abtnode/logger')('@abtnode/core:api:team');
|
|
13
|
+
const pAll = require('p-all');
|
|
13
14
|
const {
|
|
14
15
|
ROLES,
|
|
15
16
|
genPermissionName,
|
|
@@ -287,6 +288,69 @@ class TeamAPI extends EventEmitter {
|
|
|
287
288
|
const backup = Array.isArray(backups) && backups.length > 0 ? backups[0] : null;
|
|
288
289
|
const appRuntimeInfo = await this.runtimeMonitor.getBlockletRuntimeInfo(teamDid);
|
|
289
290
|
|
|
291
|
+
const [traffic, integrations, studio] = await pAll([
|
|
292
|
+
// 查询最近 30 天的 traffic insights 聚合数据
|
|
293
|
+
async () => {
|
|
294
|
+
const endDate = dayjs().format('YYYY-MM-DD');
|
|
295
|
+
const startDate = dayjs().subtract(30, 'day').format('YYYY-MM-DD');
|
|
296
|
+
const [totalRequests, failedRequests] = await Promise.all([
|
|
297
|
+
this.states.trafficInsight.sum('totalRequests', {
|
|
298
|
+
did: teamDid,
|
|
299
|
+
date: { [Op.gte]: startDate, [Op.lte]: endDate },
|
|
300
|
+
}),
|
|
301
|
+
this.states.trafficInsight.sum('failedRequests', {
|
|
302
|
+
did: teamDid,
|
|
303
|
+
date: { [Op.gte]: startDate, [Op.lte]: endDate },
|
|
304
|
+
}),
|
|
305
|
+
]);
|
|
306
|
+
return {
|
|
307
|
+
totalRequests: Number(totalRequests || 0),
|
|
308
|
+
failedRequests: Number(failedRequests || 0),
|
|
309
|
+
};
|
|
310
|
+
},
|
|
311
|
+
// 查询集成资源的数量
|
|
312
|
+
async () => {
|
|
313
|
+
const [webhookCount, accessKeyCount, oauthAppCount] = await pAll([
|
|
314
|
+
async () => {
|
|
315
|
+
const { webhookEndpointState } = await this.teamManager.getWebhookState(teamDid);
|
|
316
|
+
return webhookEndpointState.count({});
|
|
317
|
+
},
|
|
318
|
+
async () => {
|
|
319
|
+
const accessKeyState = await this.getAccessKeyState(teamDid);
|
|
320
|
+
return accessKeyState.count({});
|
|
321
|
+
},
|
|
322
|
+
async () => {
|
|
323
|
+
const { oauthClientState } = await this.teamManager.getOAuthState(teamDid);
|
|
324
|
+
return oauthClientState.count({});
|
|
325
|
+
},
|
|
326
|
+
]);
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
webhooks: webhookCount,
|
|
330
|
+
accessKeys: accessKeyCount,
|
|
331
|
+
oauthApps: oauthAppCount,
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
// 查询 Studio 创建的 Blocklet 数量
|
|
335
|
+
async () => {
|
|
336
|
+
try {
|
|
337
|
+
const { projectState, releaseState } = await this.teamManager.getProjectState(teamDid);
|
|
338
|
+
const projects = await projectState.getProjects({});
|
|
339
|
+
const projectIds = projects.map((p) => p.id);
|
|
340
|
+
const releasesCount =
|
|
341
|
+
projectIds.length > 0 ? await releaseState.count({ projectId: { [Op.in]: projectIds } }) : 0;
|
|
342
|
+
return {
|
|
343
|
+
blocklets: projects.length,
|
|
344
|
+
releases: releasesCount,
|
|
345
|
+
};
|
|
346
|
+
} catch (error) {
|
|
347
|
+
// 如果项目状态不存在或出错,忽略错误,使用默认值
|
|
348
|
+
logger.debug('Failed to get studio stats', { error, teamDid });
|
|
349
|
+
return { blocklets: 0, releases: 0 };
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
]);
|
|
353
|
+
|
|
290
354
|
return {
|
|
291
355
|
user: {
|
|
292
356
|
users,
|
|
@@ -298,6 +362,9 @@ class TeamAPI extends EventEmitter {
|
|
|
298
362
|
},
|
|
299
363
|
backup,
|
|
300
364
|
appRuntimeInfo,
|
|
365
|
+
traffic,
|
|
366
|
+
integrations,
|
|
367
|
+
studio,
|
|
301
368
|
};
|
|
302
369
|
}
|
|
303
370
|
|
|
@@ -3526,10 +3593,11 @@ class TeamAPI extends EventEmitter {
|
|
|
3526
3593
|
if (since && typeof since === 'string') {
|
|
3527
3594
|
const sinceMatch = since.match(/^(\d+)h$/);
|
|
3528
3595
|
if (sinceMatch) {
|
|
3529
|
-
|
|
3530
|
-
if (hours
|
|
3531
|
-
|
|
3596
|
+
let hours = parseInt(sinceMatch[1], 10);
|
|
3597
|
+
if (hours < 1 || hours > 24) {
|
|
3598
|
+
hours = Math.min(Math.max(hours, 1), 24);
|
|
3532
3599
|
}
|
|
3600
|
+
startTime = dayjs().subtract(hours, 'hours').toDate();
|
|
3533
3601
|
}
|
|
3534
3602
|
}
|
|
3535
3603
|
|
|
@@ -186,7 +186,6 @@ const {
|
|
|
186
186
|
const states = require('../../states');
|
|
187
187
|
const BaseBlockletManager = require('./base');
|
|
188
188
|
const { get: getEngine } = require('./engine');
|
|
189
|
-
const blockletPm2Events = require('./pm2-events');
|
|
190
189
|
const runBlockletMigrationScripts = require('../migration');
|
|
191
190
|
const hooks = require('../hooks');
|
|
192
191
|
const { BlockletRuntimeMonitor } = require('../../monitor/blocklet-runtime-monitor');
|
|
@@ -269,11 +268,6 @@ const startLock = new DBCache(() => ({
|
|
|
269
268
|
...getAbtNodeRedisAndSQLiteUrl(),
|
|
270
269
|
}));
|
|
271
270
|
|
|
272
|
-
const pm2StatusMap = {
|
|
273
|
-
online: BlockletStatus.running,
|
|
274
|
-
stop: BlockletStatus.stopped,
|
|
275
|
-
};
|
|
276
|
-
|
|
277
271
|
const getWalletAppNotification = async (blocklet, tempBlockletInfo) => {
|
|
278
272
|
let blockletInfo = tempBlockletInfo;
|
|
279
273
|
if (!blockletInfo) {
|
|
@@ -334,7 +328,6 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
334
328
|
checkUpdateQueue,
|
|
335
329
|
reportComponentUsageQueue,
|
|
336
330
|
resendNotificationQueue,
|
|
337
|
-
daemon = false,
|
|
338
331
|
teamManager,
|
|
339
332
|
nodeAPI,
|
|
340
333
|
teamAPI,
|
|
@@ -399,11 +392,6 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
399
392
|
|
|
400
393
|
this.configSynchronizer = new ConfigSynchronizer({ manager: this, states });
|
|
401
394
|
|
|
402
|
-
if (daemon) {
|
|
403
|
-
blockletPm2Events.on('online', (data) => this._syncPm2Status('online', data.blockletDid, data.componentDid));
|
|
404
|
-
blockletPm2Events.on('stop', (data) => this._syncPm2Status('stop', data.blockletDid, data.componentDid));
|
|
405
|
-
}
|
|
406
|
-
|
|
407
395
|
if (isFunction(this.checkUpdateQueue?.on)) {
|
|
408
396
|
/**
|
|
409
397
|
*
|
|
@@ -914,7 +902,8 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
914
902
|
}
|
|
915
903
|
|
|
916
904
|
try {
|
|
917
|
-
await states.blockletChild.updateChildStatus(parentBlockletId, componentDid,
|
|
905
|
+
await states.blockletChild.updateChildStatus(parentBlockletId, componentDid, {
|
|
906
|
+
status,
|
|
918
907
|
operator,
|
|
919
908
|
});
|
|
920
909
|
|
|
@@ -3449,7 +3438,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
3449
3438
|
return x;
|
|
3450
3439
|
});
|
|
3451
3440
|
|
|
3452
|
-
const blueGreenComponentIds =
|
|
3441
|
+
const blueGreenComponentIds = blueGreenGetComponentIds(oldBlocklet || blocklet, componentDids);
|
|
3453
3442
|
|
|
3454
3443
|
try {
|
|
3455
3444
|
if (process.env.ABT_NODE_DISABLE_BLUE_GREEN === 'true' || process.env.ABT_NODE_DISABLE_BLUE_GREEN === '1') {
|
|
@@ -4962,20 +4951,6 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
4962
4951
|
});
|
|
4963
4952
|
}
|
|
4964
4953
|
|
|
4965
|
-
async _syncPm2Status(pm2Status, did, componentDid) {
|
|
4966
|
-
try {
|
|
4967
|
-
const blocklet = await states.blocklet.getBlocklet(did);
|
|
4968
|
-
const component = findComponentByIdV2(blocklet, componentDid);
|
|
4969
|
-
if (component && util.shouldUpdateBlockletStatus(component.status)) {
|
|
4970
|
-
const newStatus = pm2StatusMap[pm2Status];
|
|
4971
|
-
await states.blocklet.setBlockletStatus(did, newStatus, { componentDids: [componentDid] });
|
|
4972
|
-
logger.info('sync pm2 status to blocklet', { did, componentDid, pm2Status, newStatus });
|
|
4973
|
-
}
|
|
4974
|
-
} catch (error) {
|
|
4975
|
-
logger.error('sync pm2 status to blocklet failed', { did, componentDid, pm2Status, error });
|
|
4976
|
-
}
|
|
4977
|
-
}
|
|
4978
|
-
|
|
4979
4954
|
/**
|
|
4980
4955
|
* @param {string} action install, upgrade, installComponent, upgradeComponent
|
|
4981
4956
|
* @param {string} did
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const { BlockletStatus } = require('../../../states/blocklet');
|
|
2
|
-
const { forEachBlocklet } = require('../../../util/blocklet');
|
|
3
2
|
|
|
4
|
-
const blueGreenGetComponentIds =
|
|
3
|
+
const blueGreenGetComponentIds = (blocklet, componentDids) => {
|
|
5
4
|
if (!blocklet) {
|
|
6
5
|
return {
|
|
7
6
|
componentDids: componentDids || [],
|
|
@@ -29,20 +28,16 @@ const blueGreenGetComponentIds = async (blocklet, componentDids) => {
|
|
|
29
28
|
];
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
{ parallel: true, concurrencyLimit: 10 }
|
|
45
|
-
);
|
|
31
|
+
for (const b of children) {
|
|
32
|
+
if (!componentDidsSet.has(b.meta.did)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (runningStatuses.has(b.greenStatus)) {
|
|
36
|
+
greenComponentIds.push(b.meta.did);
|
|
37
|
+
} else {
|
|
38
|
+
blueComponentIds.push(b.meta.did);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
46
41
|
|
|
47
42
|
return [
|
|
48
43
|
{
|
|
@@ -73,6 +73,8 @@ const blueGreenStartBlocklet = async (
|
|
|
73
73
|
throw new Error('No runnable component found');
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
blocklet1.children = await states.blocklet.loadChildren(blocklet1.id);
|
|
77
|
+
|
|
76
78
|
const customerDockerUseVolumeComponentIds = [];
|
|
77
79
|
const otherComponentIds = [];
|
|
78
80
|
await forEachBlockletSync(blocklet1, (b) => {
|
|
@@ -86,14 +88,6 @@ const blueGreenStartBlocklet = async (
|
|
|
86
88
|
}
|
|
87
89
|
});
|
|
88
90
|
|
|
89
|
-
if (customerDockerUseVolumeComponentIds.length) {
|
|
90
|
-
await manager.stop(
|
|
91
|
-
{ did, componentDids: customerDockerUseVolumeComponentIds, updateStatus: false, operator },
|
|
92
|
-
context
|
|
93
|
-
);
|
|
94
|
-
await manager.start({ did, componentDids: customerDockerUseVolumeComponentIds, operator }, context);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
91
|
// 分类组件 ID
|
|
98
92
|
const entryComponentIds = [];
|
|
99
93
|
const nonEntryComponentIds = [];
|
|
@@ -169,7 +163,7 @@ const blueGreenStartBlocklet = async (
|
|
|
169
163
|
return;
|
|
170
164
|
}
|
|
171
165
|
|
|
172
|
-
const blueGreenComponentIds =
|
|
166
|
+
const blueGreenComponentIds = blueGreenGetComponentIds(blocklet1, entryComponentIds);
|
|
173
167
|
|
|
174
168
|
const startedBlockletDids = [];
|
|
175
169
|
const errorBlockletDids = [];
|
|
@@ -216,16 +210,9 @@ const blueGreenStartBlocklet = async (
|
|
|
216
210
|
};
|
|
217
211
|
|
|
218
212
|
if (status === BlockletStatus.running) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
updates.stoppedAt = new Date();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
await states.blockletChild.updateChildStatus(appId, componentDid, status, isGreen, updates);
|
|
226
|
-
|
|
227
|
-
if (status === BlockletStatus.running) {
|
|
228
|
-
await states.blockletChild.updateChildStatus(appId, componentDid, BlockletStatus.stopped, !isGreen, updates);
|
|
213
|
+
await states.blockletChild.updateChildStatusRunning(appId, componentDid, isGreen, updates);
|
|
214
|
+
} else {
|
|
215
|
+
await states.blockletChild.updateChildStatusError(appId, componentDid, isGreen, updates);
|
|
229
216
|
}
|
|
230
217
|
|
|
231
218
|
// Get latest children from blocklet_children table and emit events immediately
|
|
@@ -285,6 +272,14 @@ const blueGreenStartBlocklet = async (
|
|
|
285
272
|
});
|
|
286
273
|
}
|
|
287
274
|
|
|
275
|
+
if (customerDockerUseVolumeComponentIds.length) {
|
|
276
|
+
await manager.stop(
|
|
277
|
+
{ did, componentDids: customerDockerUseVolumeComponentIds, updateStatus: false, operator },
|
|
278
|
+
context
|
|
279
|
+
);
|
|
280
|
+
await manager.start({ did, componentDids: customerDockerUseVolumeComponentIds, operator }, context);
|
|
281
|
+
}
|
|
282
|
+
|
|
288
283
|
const nextBlocklet = await manager.ensureBlocklet(did, { e2eMode });
|
|
289
284
|
|
|
290
285
|
manager.emit(BlockletEvents.statusChange, nextBlocklet);
|
|
@@ -368,7 +363,12 @@ const blueGreenStartBlocklet = async (
|
|
|
368
363
|
// Update status immediately
|
|
369
364
|
await updateChildStatusImmediately(subDid, BlockletStatus.running, item.changeToGreen);
|
|
370
365
|
|
|
371
|
-
await manager.deleteProcess({
|
|
366
|
+
await manager.deleteProcess({
|
|
367
|
+
did,
|
|
368
|
+
componentDids: [subDid],
|
|
369
|
+
isGreen: !item.changeToGreen,
|
|
370
|
+
shouldUpdateBlockletStatus: false,
|
|
371
|
+
});
|
|
372
372
|
|
|
373
373
|
logger.info('Green environment started successfully', {
|
|
374
374
|
did,
|
|
@@ -385,7 +385,12 @@ const blueGreenStartBlocklet = async (
|
|
|
385
385
|
await updateChildStatusImmediately(subDid, BlockletStatus.error, item.changeToGreen);
|
|
386
386
|
|
|
387
387
|
try {
|
|
388
|
-
await manager.deleteProcess({
|
|
388
|
+
await manager.deleteProcess({
|
|
389
|
+
did,
|
|
390
|
+
componentDids: [subDid],
|
|
391
|
+
isGreen: item.changeToGreen,
|
|
392
|
+
shouldUpdateBlockletStatus: false,
|
|
393
|
+
});
|
|
389
394
|
} catch (cleanupError) {
|
|
390
395
|
logger.error('Failed to cleanup green environment', { cleanupError });
|
|
391
396
|
}
|
|
@@ -396,7 +401,7 @@ const blueGreenStartBlocklet = async (
|
|
|
396
401
|
|
|
397
402
|
await pAll(tasks, { concurrency: 6 });
|
|
398
403
|
|
|
399
|
-
const lastBlocklet = await manager.
|
|
404
|
+
const lastBlocklet = await manager.getBlocklet(did, { e2eMode });
|
|
400
405
|
let errorDescription = '';
|
|
401
406
|
|
|
402
407
|
// 处理启动失败的组件
|
|
@@ -441,8 +446,9 @@ const blueGreenStartBlocklet = async (
|
|
|
441
446
|
|
|
442
447
|
// 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
|
|
443
448
|
if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN)) {
|
|
449
|
+
const latestBlocklet = await manager.getBlocklet(did, { e2eMode });
|
|
444
450
|
manager.emit(BlockletEvents.blurOrGreenStarted, {
|
|
445
|
-
blocklet:
|
|
451
|
+
blocklet: latestBlocklet,
|
|
446
452
|
componentDids,
|
|
447
453
|
context,
|
|
448
454
|
});
|
|
@@ -39044,7 +39044,7 @@ module.exports = require("zlib");
|
|
|
39044
39044
|
/***/ ((module) => {
|
|
39045
39045
|
|
|
39046
39046
|
"use strict";
|
|
39047
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.17.4","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.17.4","@abtnode/auth":"1.17.4","@abtnode/certificate-manager":"1.17.4","@abtnode/constant":"1.17.4","@abtnode/cron":"1.17.4","@abtnode/db-cache":"1.17.4","@abtnode/docker-utils":"1.17.4","@abtnode/logger":"1.17.4","@abtnode/models":"1.17.4","@abtnode/queue":"1.17.4","@abtnode/rbac":"1.17.4","@abtnode/router-provider":"1.17.4","@abtnode/static-server":"1.17.4","@abtnode/timemachine":"1.17.4","@abtnode/util":"1.17.4","@aigne/aigne-hub":"^0.10.
|
|
39047
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.17.4","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.17.4","@abtnode/auth":"1.17.4","@abtnode/certificate-manager":"1.17.4","@abtnode/constant":"1.17.4","@abtnode/cron":"1.17.4","@abtnode/db-cache":"1.17.4","@abtnode/docker-utils":"1.17.4","@abtnode/logger":"1.17.4","@abtnode/models":"1.17.4","@abtnode/queue":"1.17.4","@abtnode/rbac":"1.17.4","@abtnode/router-provider":"1.17.4","@abtnode/static-server":"1.17.4","@abtnode/timemachine":"1.17.4","@abtnode/util":"1.17.4","@aigne/aigne-hub":"^0.10.14","@arcblock/did":"^1.27.14","@arcblock/did-connect-js":"^1.27.14","@arcblock/did-ext":"^1.27.14","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"^1.27.14","@arcblock/event-hub":"^1.27.14","@arcblock/jwt":"^1.27.14","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"^1.27.14","@arcblock/vc":"^1.27.14","@blocklet/constant":"1.17.4","@blocklet/did-space-js":"^1.2.9","@blocklet/env":"1.17.4","@blocklet/error":"^0.3.4","@blocklet/meta":"1.17.4","@blocklet/resolver":"1.17.4","@blocklet/sdk":"1.17.4","@blocklet/server-js":"1.17.4","@blocklet/store":"1.17.4","@blocklet/theme":"^3.2.13","@fidm/x509":"^1.2.1","@ocap/mcrypto":"^1.27.14","@ocap/util":"^1.27.14","@ocap/wallet":"^1.27.14","@slack/webhook":"^7.0.6","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","private-ip":"^2.3.4","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"^2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^11.1.0","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"axios-mock-adapter":"^2.1.0","expand-tilde":"^2.0.2","express":"^4.18.2","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
|
|
39048
39048
|
|
|
39049
39049
|
/***/ }),
|
|
39050
39050
|
|
package/lib/states/audit-log.js
CHANGED
|
@@ -11,6 +11,7 @@ const isString = require('lodash/isString');
|
|
|
11
11
|
const mapValues = require('lodash/mapValues');
|
|
12
12
|
const map = require('lodash/map');
|
|
13
13
|
const omit = require('lodash/omit');
|
|
14
|
+
const { Joi } = require('@arcblock/validator');
|
|
14
15
|
const { joinURL } = require('ufo');
|
|
15
16
|
const { Op } = require('sequelize');
|
|
16
17
|
const { getDisplayName } = require('@blocklet/meta/lib/util');
|
|
@@ -22,6 +23,8 @@ const BaseState = require('./base');
|
|
|
22
23
|
const { parse } = require('../util/ua');
|
|
23
24
|
const { getScope } = require('../util/audit-log');
|
|
24
25
|
|
|
26
|
+
const emailSchema = Joi.string().email().required();
|
|
27
|
+
|
|
25
28
|
const getServerInfo = (info) =>
|
|
26
29
|
`[${info.name}](${joinURL(process.env.NODE_ENV === 'production' ? info.routing.adminPath : '', '/settings/about')})`;
|
|
27
30
|
/**
|
|
@@ -232,15 +235,26 @@ const getTaggingInfo = async (args, node, info) => {
|
|
|
232
235
|
/**
|
|
233
236
|
* 隐藏私密信息,主要字段有
|
|
234
237
|
* 1. email
|
|
238
|
+
* 2. password
|
|
235
239
|
*/
|
|
236
|
-
const hidePrivateInfo = (result) => {
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
const hidePrivateInfo = (result, fields = []) => {
|
|
241
|
+
// 默认需要隐藏的字段(不区分大小写匹配)
|
|
242
|
+
const defaultFields = ['email', 'password'];
|
|
243
|
+
// 合并默认字段和自定义字段
|
|
244
|
+
const sensitiveFields = [...defaultFields, ...fields].map((f) => f.toLowerCase());
|
|
245
|
+
|
|
246
|
+
// 检查字段名是否需要隐藏
|
|
247
|
+
const isSensitiveField = (key) => {
|
|
248
|
+
if (!key) return false;
|
|
249
|
+
const lowerKey = key.toLowerCase();
|
|
250
|
+
return sensitiveFields.some((field) => lowerKey.includes(field));
|
|
251
|
+
};
|
|
239
252
|
|
|
240
253
|
const processValue = (value, key) => {
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
254
|
+
// 如果字段名匹配敏感字段,则隐藏
|
|
255
|
+
// 使用反引号包裹,以代码样式显示星号,避免被 Markdown 渲染为加粗/斜体
|
|
256
|
+
if (isSensitiveField(key) || (isString(value) && !emailSchema.validate(value).error)) {
|
|
257
|
+
return '`***`';
|
|
244
258
|
}
|
|
245
259
|
|
|
246
260
|
// 递归处理对象
|
|
@@ -351,8 +365,19 @@ const getLogContent = async (action, args, context, result, info, node) => {
|
|
|
351
365
|
return `blocklet ${getBlockletInfo(result, info)} audit federated login member ${args.memberPid} with status: ${
|
|
352
366
|
args.status
|
|
353
367
|
}`;
|
|
354
|
-
case 'configNotification':
|
|
355
|
-
|
|
368
|
+
case 'configNotification': {
|
|
369
|
+
const { notification } = args;
|
|
370
|
+
const notificationObject = JSON.parse(notification || '{}');
|
|
371
|
+
const resultObj = {};
|
|
372
|
+
if (notificationObject.email) {
|
|
373
|
+
resultObj.email = hidePrivateInfo(notificationObject.email, ['from', 'host', 'password']);
|
|
374
|
+
}
|
|
375
|
+
if (notificationObject.push) {
|
|
376
|
+
resultObj.push = hidePrivateInfo(notificationObject.push);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return `updated following notification setting: ${JSON.stringify(resultObj)}`;
|
|
380
|
+
}
|
|
356
381
|
case 'updateComponentTitle':
|
|
357
382
|
return `update component title to **${args.title}**`;
|
|
358
383
|
case 'updateComponentMountPoint':
|
|
@@ -1031,7 +1056,7 @@ class AuditLogState extends BaseState {
|
|
|
1031
1056
|
componentDid: context?.user?.componentDid || null,
|
|
1032
1057
|
});
|
|
1033
1058
|
|
|
1034
|
-
logger.info('create',
|
|
1059
|
+
logger.info('create', { action, userDid: actor.did, componentDid: context?.user?.componentDid || null });
|
|
1035
1060
|
return resolve(data);
|
|
1036
1061
|
} catch (err) {
|
|
1037
1062
|
logger.error('create error', { error: err, action, args, context });
|
|
@@ -35,30 +35,109 @@ class BlockletChildState extends BaseState {
|
|
|
35
35
|
return this.remove({ parentBlockletId });
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
async updateChildStatusRunning(parentBlockletId, childDid, isGreen, additionalUpdates = {}) {
|
|
39
|
+
const now = new Date();
|
|
40
|
+
const baseUpdates = {
|
|
41
|
+
...additionalUpdates,
|
|
42
|
+
updatedAt: now,
|
|
43
|
+
startedAt: now,
|
|
44
|
+
stoppedAt: null,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (isGreen) {
|
|
48
|
+
// 绿环境启动成功 -> 绿 running,蓝 stopped
|
|
49
|
+
await this.update(
|
|
50
|
+
{ parentBlockletId, childDid },
|
|
51
|
+
{
|
|
52
|
+
$set: {
|
|
53
|
+
...baseUpdates,
|
|
54
|
+
greenStatus: BlockletStatus.running,
|
|
55
|
+
status: BlockletStatus.stopped,
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
} else {
|
|
60
|
+
// 蓝环境启动成功 -> 蓝 running,绿 stopped
|
|
61
|
+
await this.update(
|
|
62
|
+
{ parentBlockletId, childDid },
|
|
63
|
+
{
|
|
64
|
+
$set: {
|
|
65
|
+
...baseUpdates,
|
|
66
|
+
greenStatus: BlockletStatus.stopped,
|
|
67
|
+
status: BlockletStatus.running,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async updateChildStatusError(parentBlockletId, childDid, isGreen, additionalUpdates = {}) {
|
|
75
|
+
const now = new Date();
|
|
76
|
+
const baseUpdates = {
|
|
77
|
+
...additionalUpdates,
|
|
78
|
+
updatedAt: now,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (isGreen) {
|
|
82
|
+
// 绿环境启动失败
|
|
83
|
+
await this.update(
|
|
84
|
+
{ parentBlockletId, childDid },
|
|
85
|
+
{
|
|
86
|
+
$set: {
|
|
87
|
+
...baseUpdates,
|
|
88
|
+
greenStatus: BlockletStatus.error,
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
// 蓝环境启动失败
|
|
94
|
+
await this.update(
|
|
95
|
+
{ parentBlockletId, childDid },
|
|
96
|
+
{
|
|
97
|
+
$set: {
|
|
98
|
+
...baseUpdates,
|
|
99
|
+
status: BlockletStatus.error,
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
38
106
|
/**
|
|
39
|
-
* Update child status
|
|
107
|
+
* Update child status only (without overwriting other fields)
|
|
108
|
+
* Used by setBlockletStatus to avoid race conditions in multi-process environments
|
|
40
109
|
* @param {string} parentBlockletId - The parent blocklet ID
|
|
41
110
|
* @param {string} childDid - The child DID
|
|
42
|
-
* @param {
|
|
43
|
-
* @param {
|
|
44
|
-
* @param {
|
|
111
|
+
* @param {Object} options - Update options
|
|
112
|
+
* @param {number} options.status - The blue status to set
|
|
113
|
+
* @param {number} options.greenStatus - The green status to set
|
|
114
|
+
* @param {boolean} options.isGreen - Whether to update green status
|
|
115
|
+
* @param {boolean} options.isGreenAndBlue - Whether to update both statuses
|
|
116
|
+
* @param {string} options.operator - The operator
|
|
45
117
|
* @returns {Promise<Object>} - Updated child
|
|
46
118
|
*/
|
|
47
|
-
async updateChildStatus(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!child) {
|
|
119
|
+
async updateChildStatus(
|
|
120
|
+
parentBlockletId,
|
|
121
|
+
childDid,
|
|
122
|
+
{ status, isGreen = false, isGreenAndBlue = false, operator } = {}
|
|
123
|
+
) {
|
|
124
|
+
if (!parentBlockletId || !childDid) {
|
|
55
125
|
return null;
|
|
56
126
|
}
|
|
57
127
|
|
|
58
128
|
const updates = {
|
|
59
|
-
|
|
129
|
+
updatedAt: new Date(),
|
|
130
|
+
inProgressStart: Date.now(),
|
|
60
131
|
};
|
|
61
|
-
|
|
132
|
+
|
|
133
|
+
if (operator) {
|
|
134
|
+
updates.operator = operator;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (isGreenAndBlue) {
|
|
138
|
+
updates.status = status;
|
|
139
|
+
updates.greenStatus = status;
|
|
140
|
+
} else if (isGreen) {
|
|
62
141
|
updates.greenStatus = status;
|
|
63
142
|
} else {
|
|
64
143
|
updates.status = status;
|
|
@@ -72,47 +151,42 @@ class BlockletChildState extends BaseState {
|
|
|
72
151
|
updates.stoppedAt = new Date();
|
|
73
152
|
}
|
|
74
153
|
|
|
75
|
-
const [, [updated]] = await this.update({
|
|
154
|
+
const [, [updated]] = await this.update({ parentBlockletId, childDid }, { $set: updates });
|
|
76
155
|
return updated;
|
|
77
156
|
}
|
|
78
157
|
|
|
79
158
|
/**
|
|
80
|
-
*
|
|
159
|
+
* Update child ports only (without affecting status fields)
|
|
160
|
+
* Used by refreshBlockletPorts to avoid overwriting status during concurrent operations
|
|
81
161
|
* @param {string} parentBlockletId - The parent blocklet ID
|
|
82
|
-
* @param {
|
|
83
|
-
* @param {
|
|
84
|
-
* @param {Object}
|
|
85
|
-
* @returns {Promise<
|
|
162
|
+
* @param {string} childDid - The child DID
|
|
163
|
+
* @param {Object} ports - The ports to set (for blue environment)
|
|
164
|
+
* @param {Object} greenPorts - The green ports to set (for green environment)
|
|
165
|
+
* @returns {Promise<Object>} - Updated child
|
|
86
166
|
*/
|
|
87
|
-
async
|
|
88
|
-
if (!parentBlockletId) {
|
|
89
|
-
return
|
|
167
|
+
async updateChildPorts(parentBlockletId, childDid, { ports, greenPorts } = {}) {
|
|
168
|
+
if (!parentBlockletId || !childDid) {
|
|
169
|
+
return null;
|
|
90
170
|
}
|
|
91
|
-
const children = await this.getChildrenByParentId(parentBlockletId);
|
|
92
|
-
const updates = [];
|
|
93
|
-
|
|
94
|
-
for (const child of children) {
|
|
95
|
-
if (childDids.includes(child.childDid)) {
|
|
96
|
-
const childUpdates = {
|
|
97
|
-
status,
|
|
98
|
-
...additionalFields,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (status === BlockletStatus.running) {
|
|
102
|
-
childUpdates.startedAt = new Date();
|
|
103
|
-
childUpdates.stoppedAt = null;
|
|
104
|
-
} else if (status === BlockletStatus.stopped) {
|
|
105
|
-
childUpdates.startedAt = null;
|
|
106
|
-
childUpdates.stoppedAt = new Date();
|
|
107
|
-
}
|
|
108
171
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
172
|
+
const updates = {
|
|
173
|
+
updatedAt: new Date(),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (ports !== undefined) {
|
|
177
|
+
updates.ports = ports;
|
|
178
|
+
}
|
|
179
|
+
if (greenPorts !== undefined) {
|
|
180
|
+
updates.greenPorts = greenPorts;
|
|
113
181
|
}
|
|
114
182
|
|
|
115
|
-
|
|
183
|
+
// Only update if there's something to update
|
|
184
|
+
if (Object.keys(updates).length <= 1) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const [, [updated]] = await this.update({ parentBlockletId, childDid }, { $set: updates });
|
|
189
|
+
return updated;
|
|
116
190
|
}
|
|
117
191
|
}
|
|
118
192
|
|
|
@@ -5,12 +5,20 @@ const logger = require('@abtnode/logger')('@abtnode/core:states:blocklet-extras'
|
|
|
5
5
|
const camelCase = require('lodash/camelCase');
|
|
6
6
|
const get = require('lodash/get');
|
|
7
7
|
const { CustomError } = require('@blocklet/error');
|
|
8
|
+
const security = require('@abtnode/util/lib/security');
|
|
9
|
+
const cloneDeep = require('@abtnode/util/lib/deep-clone');
|
|
8
10
|
|
|
9
11
|
const BaseState = require('./base');
|
|
10
12
|
|
|
11
13
|
const { mergeConfigs, parseConfigs, encryptConfigs } = require('../blocklet/extras');
|
|
12
14
|
const { validateAddMeta } = require('../validators/blocklet-extra');
|
|
13
15
|
|
|
16
|
+
// settings 中需要加密的字段路径
|
|
17
|
+
const SETTINGS_SECURE_FIELDS = ['notification.email.password'];
|
|
18
|
+
|
|
19
|
+
// 加密数据的前缀标记,用于识别数据是否已加密
|
|
20
|
+
const ENCRYPTED_PREFIX = 'ENC:';
|
|
21
|
+
|
|
14
22
|
const noop = (k) => (v) => v[k];
|
|
15
23
|
|
|
16
24
|
/**
|
|
@@ -31,15 +39,69 @@ class BlockletExtrasState extends BaseState {
|
|
|
31
39
|
// setting
|
|
32
40
|
{
|
|
33
41
|
name: 'settings',
|
|
34
|
-
beforeSet: ({ old, cur }) => {
|
|
42
|
+
beforeSet: ({ old, cur, did, dek }) => {
|
|
35
43
|
const merged = { ...old, ...cur };
|
|
36
44
|
Object.keys(merged).forEach((key) => {
|
|
37
45
|
if (merged[key] === undefined || merged[key] === null) {
|
|
38
46
|
delete merged[key];
|
|
39
47
|
}
|
|
40
48
|
});
|
|
49
|
+
|
|
50
|
+
// 对敏感字段进行加密
|
|
51
|
+
const enableSecurity = dek && did;
|
|
52
|
+
if (enableSecurity) {
|
|
53
|
+
SETTINGS_SECURE_FIELDS.forEach((fieldPath) => {
|
|
54
|
+
const value = get(merged, fieldPath);
|
|
55
|
+
// 只加密 cur 中传入的新值,避免重复加密已存储的旧值
|
|
56
|
+
const newValue = get(cur, fieldPath);
|
|
57
|
+
if (newValue !== undefined && value) {
|
|
58
|
+
const keys = fieldPath.split('.');
|
|
59
|
+
let target = merged;
|
|
60
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
61
|
+
target = target[keys[i]];
|
|
62
|
+
}
|
|
63
|
+
// 添加前缀标记,用于识别已加密的数据
|
|
64
|
+
const encrypted = ENCRYPTED_PREFIX + security.encrypt(String(value), did, dek);
|
|
65
|
+
target[keys[keys.length - 1]] = encrypted;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
return merged;
|
|
42
71
|
},
|
|
72
|
+
afterGet: ({ data, did, dek }) => {
|
|
73
|
+
if (!data) {
|
|
74
|
+
return data;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 对敏感字段进行解密
|
|
78
|
+
const enableSecurity = dek && did;
|
|
79
|
+
if (enableSecurity) {
|
|
80
|
+
const result = cloneDeep(data);
|
|
81
|
+
SETTINGS_SECURE_FIELDS.forEach((fieldPath) => {
|
|
82
|
+
const value = get(result, fieldPath);
|
|
83
|
+
// 只有带有加密前缀的数据才需要解密,未加密的历史数据保持原值
|
|
84
|
+
if (value && typeof value === 'string' && value.startsWith(ENCRYPTED_PREFIX)) {
|
|
85
|
+
try {
|
|
86
|
+
const keys = fieldPath.split('.');
|
|
87
|
+
let target = result;
|
|
88
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
89
|
+
target = target[keys[i]];
|
|
90
|
+
}
|
|
91
|
+
// 去掉前缀后解密
|
|
92
|
+
const encryptedValue = value.slice(ENCRYPTED_PREFIX.length);
|
|
93
|
+
target[keys[keys.length - 1]] = security.decrypt(encryptedValue, did, dek);
|
|
94
|
+
} catch {
|
|
95
|
+
// 解密失败,保持原值(去掉前缀)
|
|
96
|
+
logger.warn('Failed to decrypt settings field', { fieldPath });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return data;
|
|
104
|
+
},
|
|
43
105
|
},
|
|
44
106
|
];
|
|
45
107
|
|
package/lib/states/blocklet.js
CHANGED
|
@@ -300,6 +300,7 @@ class BlockletState extends BaseState {
|
|
|
300
300
|
mode: child.mode || 'production',
|
|
301
301
|
status: child.status || 0,
|
|
302
302
|
ports: child.ports || {},
|
|
303
|
+
environments: child.environments || [],
|
|
303
304
|
children: child.children || [],
|
|
304
305
|
migratedFrom: child.migratedFrom || [],
|
|
305
306
|
installedAt: child.installedAt,
|
|
@@ -384,12 +385,11 @@ class BlockletState extends BaseState {
|
|
|
384
385
|
if (child.deployedFrom !== undefined) updates.deployedFrom = child.deployedFrom;
|
|
385
386
|
if (child.mode !== undefined) updates.mode = child.mode;
|
|
386
387
|
if (child.ports !== undefined) updates.ports = child.ports;
|
|
387
|
-
if (child.
|
|
388
|
-
if (child.migratedFrom !== undefined) updates.migratedFrom = child.migratedFrom;
|
|
388
|
+
if (child.environments !== undefined) updates.environments = child.environments;
|
|
389
389
|
|
|
390
390
|
// Only update status-related fields if explicitly provided
|
|
391
391
|
if (child.status !== undefined) updates.status = child.status;
|
|
392
|
-
|
|
392
|
+
// Note: installedAt should only be set on first install, never updated
|
|
393
393
|
if (child.startedAt !== undefined) updates.startedAt = child.startedAt;
|
|
394
394
|
if (child.stoppedAt !== undefined) updates.stoppedAt = child.stoppedAt;
|
|
395
395
|
if (child.pausedAt !== undefined) updates.pausedAt = child.pausedAt;
|
|
@@ -418,9 +418,8 @@ class BlockletState extends BaseState {
|
|
|
418
418
|
mode: child.mode || 'production',
|
|
419
419
|
status: child.status || 0,
|
|
420
420
|
ports: child.ports || {},
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
installedAt: child.installedAt,
|
|
421
|
+
environments: child.environments || [],
|
|
422
|
+
installedAt: new Date(),
|
|
424
423
|
startedAt: child.startedAt,
|
|
425
424
|
stoppedAt: child.stoppedAt,
|
|
426
425
|
pausedAt: child.pausedAt,
|
|
@@ -865,8 +864,17 @@ class BlockletState extends BaseState {
|
|
|
865
864
|
if (actuallyRefreshedDids.length > 0) {
|
|
866
865
|
await this.updateBlocklet(did, {});
|
|
867
866
|
|
|
868
|
-
//
|
|
869
|
-
|
|
867
|
+
// Only update ports/greenPorts to avoid overwriting status during concurrent operations
|
|
868
|
+
if (this.BlockletChildState) {
|
|
869
|
+
for (const component of blocklet.children) {
|
|
870
|
+
if (actuallyRefreshedDids.includes(component.meta?.did)) {
|
|
871
|
+
await this.BlockletChildState.updateChildPorts(blocklet.id, component.meta.did, {
|
|
872
|
+
ports: component.ports,
|
|
873
|
+
greenPorts: component.greenPorts,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
870
878
|
}
|
|
871
879
|
|
|
872
880
|
return {
|
|
@@ -986,6 +994,8 @@ class BlockletState extends BaseState {
|
|
|
986
994
|
return res;
|
|
987
995
|
}
|
|
988
996
|
|
|
997
|
+
// Collect components to update
|
|
998
|
+
const componentsToUpdate = [];
|
|
989
999
|
for (const component of doc.children || []) {
|
|
990
1000
|
if (component.meta.group === BlockletGroup.gateway) {
|
|
991
1001
|
continue;
|
|
@@ -995,6 +1005,9 @@ class BlockletState extends BaseState {
|
|
|
995
1005
|
continue;
|
|
996
1006
|
}
|
|
997
1007
|
|
|
1008
|
+
componentsToUpdate.push(component.meta.did);
|
|
1009
|
+
|
|
1010
|
+
// Update in-memory for return value
|
|
998
1011
|
component[isGreen ? 'greenStatus' : 'status'] = status;
|
|
999
1012
|
if (isGreenAndBlue) {
|
|
1000
1013
|
component.greenStatus = status;
|
|
@@ -1020,11 +1033,26 @@ class BlockletState extends BaseState {
|
|
|
1020
1033
|
updateData.status = status;
|
|
1021
1034
|
}
|
|
1022
1035
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1036
|
+
// Update blocklet without children to avoid overwriting status during concurrent operations
|
|
1025
1037
|
const res = await this.updateBlocklet(did, updateData);
|
|
1026
1038
|
|
|
1027
|
-
|
|
1039
|
+
// Update each component's status individually using BlockletChildState
|
|
1040
|
+
if (this.BlockletChildState && componentsToUpdate.length > 0) {
|
|
1041
|
+
for (const componentDid of componentsToUpdate) {
|
|
1042
|
+
await this.BlockletChildState.updateChildStatus(doc.id, componentDid, {
|
|
1043
|
+
status,
|
|
1044
|
+
isGreen,
|
|
1045
|
+
isGreenAndBlue,
|
|
1046
|
+
operator,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const children = await this.loadChildren(doc.id);
|
|
1052
|
+
|
|
1053
|
+
res.children = children;
|
|
1054
|
+
// Recalculate status after children are loaded with updated status
|
|
1055
|
+
res.status = getBlockletStatus(res);
|
|
1028
1056
|
return res;
|
|
1029
1057
|
} finally {
|
|
1030
1058
|
await lock.releaseLock(lockName);
|
|
@@ -965,11 +965,13 @@ class NotificationState extends BaseState {
|
|
|
965
965
|
throw new Error('Invalid since format. Expected format: "1h", "2h", "24h", etc.');
|
|
966
966
|
}
|
|
967
967
|
|
|
968
|
-
|
|
968
|
+
let hours = parseInt(sinceMatch[1], 10);
|
|
969
969
|
|
|
970
970
|
// 验证范围:最小 1h,最大 24h
|
|
971
971
|
if (hours < 1 || hours > 24) {
|
|
972
|
-
|
|
972
|
+
logger.warn('The since parameter must be between 1h and 24h.');
|
|
973
|
+
// 限制 hours 在 1-24 范围内
|
|
974
|
+
hours = Math.min(Math.max(hours, 1), 24);
|
|
973
975
|
}
|
|
974
976
|
|
|
975
977
|
// 计算时间范围
|
package/lib/util/blocklet.js
CHANGED
|
@@ -1892,6 +1892,7 @@ const formatBlockletTheme = (rawTheme) => {
|
|
|
1892
1892
|
themeConfig = {
|
|
1893
1893
|
...concept.themeConfig,
|
|
1894
1894
|
prefer: concept.prefer,
|
|
1895
|
+
name: concept.name,
|
|
1895
1896
|
};
|
|
1896
1897
|
} else {
|
|
1897
1898
|
// 兼容旧数据
|
|
@@ -1900,6 +1901,7 @@ const formatBlockletTheme = (rawTheme) => {
|
|
|
1900
1901
|
dark: rawTheme.dark || {},
|
|
1901
1902
|
common: rawTheme.common || {},
|
|
1902
1903
|
prefer: rawTheme.prefer || 'system',
|
|
1904
|
+
name: rawTheme.name || 'Default',
|
|
1903
1905
|
};
|
|
1904
1906
|
}
|
|
1905
1907
|
}
|
|
@@ -14,6 +14,105 @@ const notCheckPrimaryKeyTableNames = new Set(['tagging']);
|
|
|
14
14
|
|
|
15
15
|
const needBreakErrors = [];
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Generate a unique ID for blocklet_children records
|
|
19
|
+
*/
|
|
20
|
+
function generateChildId() {
|
|
21
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 因为删除了表字段,所以需要单独处理,不然 migrate 有旧的 children 数据会失败
|
|
26
|
+
* @param {object} params - Parameters
|
|
27
|
+
* @param {Sequelize} params.pgDb - PostgreSQL database connection
|
|
28
|
+
* @param {string} params.blockletId - Parent blocklet ID
|
|
29
|
+
* @param {string} params.parentBlockletDid - Parent blocklet DID
|
|
30
|
+
* @param {Array} params.children - Children array to migrate
|
|
31
|
+
*/
|
|
32
|
+
async function migrateBlockletChildrenToTable({ pgDb, blockletId, parentBlockletDid, children }) {
|
|
33
|
+
if (!Array.isArray(children) || children.length === 0) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const child of children) {
|
|
38
|
+
const childMeta = child?.meta || {};
|
|
39
|
+
const childDid = childMeta?.did;
|
|
40
|
+
|
|
41
|
+
if (!childDid) {
|
|
42
|
+
console.warn(` ⚠️ Child in blocklet ${blockletId} has no meta.did, skipping`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Check if child already exists
|
|
48
|
+
const [existing] = await pgDb.query(
|
|
49
|
+
'SELECT id FROM blocklet_children WHERE "parentBlockletId" = $1 AND "childDid" = $2 LIMIT 1',
|
|
50
|
+
{ bind: [blockletId, childDid], type: QueryTypes.SELECT }
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (existing) {
|
|
54
|
+
console.log(` ℹ️ Child ${childDid} already exists for blocklet ${blockletId}, skipping`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Insert child record
|
|
59
|
+
const insertSQL = `
|
|
60
|
+
INSERT INTO blocklet_children (
|
|
61
|
+
id, "parentBlockletId", "parentBlockletDid", "childDid", "mountPoint",
|
|
62
|
+
meta, "bundleSource", source, "deployedFrom", mode, status,
|
|
63
|
+
ports, environments, "installedAt", "startedAt",
|
|
64
|
+
"stoppedAt", "pausedAt", operator, "inProgressStart", "greenStatus",
|
|
65
|
+
"greenPorts", "createdAt", "updatedAt"
|
|
66
|
+
) VALUES (
|
|
67
|
+
$1, $2, $3, $4, $5,
|
|
68
|
+
$6::jsonb, $7::jsonb, $8, $9, $10, $11,
|
|
69
|
+
$12::jsonb, $13::jsonb, $14, $15,
|
|
70
|
+
$16, $17, $18, $19, $20,
|
|
71
|
+
$21::jsonb, $22, $23
|
|
72
|
+
)
|
|
73
|
+
ON CONFLICT DO NOTHING
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const now = new Date();
|
|
77
|
+
const bindValues = [
|
|
78
|
+
generateChildId(), // id
|
|
79
|
+
blockletId, // parentBlockletId
|
|
80
|
+
parentBlockletDid, // parentBlockletDid
|
|
81
|
+
childDid, // childDid
|
|
82
|
+
child.mountPoint || null, // mountPoint
|
|
83
|
+
JSON.stringify(child.meta || {}), // meta
|
|
84
|
+
JSON.stringify(child.bundleSource || {}), // bundleSource
|
|
85
|
+
child.source || 0, // source
|
|
86
|
+
child.deployedFrom || '', // deployedFrom
|
|
87
|
+
child.mode || 'production', // mode
|
|
88
|
+
child.status || 0, // status
|
|
89
|
+
JSON.stringify(child.ports || {}), // ports
|
|
90
|
+
JSON.stringify(child.environments || []), // environments
|
|
91
|
+
child.installedAt || null, // installedAt
|
|
92
|
+
child.startedAt || null, // startedAt
|
|
93
|
+
child.stoppedAt || null, // stoppedAt
|
|
94
|
+
child.pausedAt || null, // pausedAt
|
|
95
|
+
child.operator || null, // operator
|
|
96
|
+
child.inProgressStart || null, // inProgressStart
|
|
97
|
+
child.greenStatus || null, // greenStatus
|
|
98
|
+
child.greenPorts ? JSON.stringify(child.greenPorts) : null, // greenPorts
|
|
99
|
+
now, // createdAt
|
|
100
|
+
now, // updatedAt
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
await pgDb.query(insertSQL, { bind: bindValues });
|
|
104
|
+
console.log(` ✅ Migrated child ${childDid} to blocklet_children table`);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
// Ignore unique constraint errors
|
|
107
|
+
if (err.name === 'SequelizeUniqueConstraintError' || err.message?.includes('UNIQUE constraint')) {
|
|
108
|
+
console.log(` ℹ️ Child ${childDid} already exists (unique constraint), skipping`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
console.error(` ❌ Failed to migrate child ${childDid}:`, err.message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
17
116
|
function sortTableNames(tableNames, sort) {
|
|
18
117
|
return [...tableNames].sort((a, b) => {
|
|
19
118
|
const indexA = sort.indexOf(a);
|
|
@@ -50,7 +149,8 @@ async function migrateAllTablesNoModels(dbPath) {
|
|
|
50
149
|
.filter((name) => !/^(sqlite|sequelize)/.test(name.toLowerCase()) && name !== 'runtime_insights');
|
|
51
150
|
|
|
52
151
|
// 把 tableNames 排序, 把被依赖的表放前面
|
|
53
|
-
|
|
152
|
+
// blocklet_children 需要在 blocklets 之前处理,因为 blocklets 的 children 字段需要迁移到 blocklet_children 表
|
|
153
|
+
tableNames = sortTableNames(tableNames, ['users', 'notification_receivers', 'blocklet_children', 'blocklets']);
|
|
54
154
|
|
|
55
155
|
for (const tableName of tableNames) {
|
|
56
156
|
console.log(`\n➡️ Starting migration for table: ${dbPath} ${tableName}`);
|
|
@@ -71,6 +171,14 @@ async function migrateAllTablesNoModels(dbPath) {
|
|
|
71
171
|
if (dbPath.includes('server.db') && tableName === 'blocklets') {
|
|
72
172
|
allCols = allCols.filter((c) => c !== 'controller');
|
|
73
173
|
}
|
|
174
|
+
|
|
175
|
+
// 删除 blocklets 表中的 children 列, 因为 children 已经拆分到 blocklet_children 表
|
|
176
|
+
// children 数据会在迁移过程中单独处理
|
|
177
|
+
const hasChildrenColumn = tableName === 'blocklets' && sqliteSchema.children;
|
|
178
|
+
if (hasChildrenColumn) {
|
|
179
|
+
allCols = allCols.filter((c) => c !== 'children');
|
|
180
|
+
console.log(' ℹ️ Detected children column in blocklets table, will migrate to blocklet_children table');
|
|
181
|
+
}
|
|
74
182
|
let pkCols = allCols.filter((c) => sqliteSchema[c].primaryKey);
|
|
75
183
|
if (!pkCols.length) {
|
|
76
184
|
pkCols = [allCols[0]];
|
|
@@ -167,6 +275,59 @@ async function migrateAllTablesNoModels(dbPath) {
|
|
|
167
275
|
console.log(` Migrating rows ${offset + 1}-${offset + rows.length}`);
|
|
168
276
|
|
|
169
277
|
for (const row of rows) {
|
|
278
|
+
// Handle children migration for blocklets table
|
|
279
|
+
if (hasChildrenColumn && row.children) {
|
|
280
|
+
try {
|
|
281
|
+
let children = row.children;
|
|
282
|
+
if (typeof children === 'string') {
|
|
283
|
+
try {
|
|
284
|
+
children = JSON.parse(children);
|
|
285
|
+
} catch {
|
|
286
|
+
children = null;
|
|
287
|
+
}
|
|
288
|
+
} else if (Buffer.isBuffer(children)) {
|
|
289
|
+
try {
|
|
290
|
+
children = JSON.parse(children.toString('utf8'));
|
|
291
|
+
} catch {
|
|
292
|
+
children = null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
297
|
+
// Get parent blocklet DID from meta
|
|
298
|
+
let meta = row.meta;
|
|
299
|
+
if (typeof meta === 'string') {
|
|
300
|
+
try {
|
|
301
|
+
meta = JSON.parse(meta);
|
|
302
|
+
} catch {
|
|
303
|
+
meta = {};
|
|
304
|
+
}
|
|
305
|
+
} else if (Buffer.isBuffer(meta)) {
|
|
306
|
+
try {
|
|
307
|
+
meta = JSON.parse(meta.toString('utf8'));
|
|
308
|
+
} catch {
|
|
309
|
+
meta = {};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const parentBlockletDid = meta?.did;
|
|
314
|
+
if (parentBlockletDid) {
|
|
315
|
+
console.log(` 🔄 Migrating ${children.length} children for blocklet ${row.id}`);
|
|
316
|
+
await migrateBlockletChildrenToTable({
|
|
317
|
+
pgDb,
|
|
318
|
+
blockletId: row.id,
|
|
319
|
+
parentBlockletDid,
|
|
320
|
+
children,
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
console.warn(` ⚠️ Blocklet ${row.id} has no meta.did, skipping children migration`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.error(` ❌ Failed to migrate children for blocklet ${row.id}:`, err.message);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
170
331
|
// Fix invalid date values for all DATE/TIMESTAMP columns
|
|
171
332
|
for (const dateCol of dateCols) {
|
|
172
333
|
if (row[dateCol] != null) {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.17.5-beta-
|
|
6
|
+
"version": "1.17.5-beta-20251214-231110-497f8d27",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -17,46 +17,46 @@
|
|
|
17
17
|
"author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
|
|
18
18
|
"license": "Apache-2.0",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@abtnode/analytics": "1.17.5-beta-
|
|
21
|
-
"@abtnode/auth": "1.17.5-beta-
|
|
22
|
-
"@abtnode/certificate-manager": "1.17.5-beta-
|
|
23
|
-
"@abtnode/constant": "1.17.5-beta-
|
|
24
|
-
"@abtnode/cron": "1.17.5-beta-
|
|
25
|
-
"@abtnode/db-cache": "1.17.5-beta-
|
|
26
|
-
"@abtnode/docker-utils": "1.17.5-beta-
|
|
27
|
-
"@abtnode/logger": "1.17.5-beta-
|
|
28
|
-
"@abtnode/models": "1.17.5-beta-
|
|
29
|
-
"@abtnode/queue": "1.17.5-beta-
|
|
30
|
-
"@abtnode/rbac": "1.17.5-beta-
|
|
31
|
-
"@abtnode/router-provider": "1.17.5-beta-
|
|
32
|
-
"@abtnode/static-server": "1.17.5-beta-
|
|
33
|
-
"@abtnode/timemachine": "1.17.5-beta-
|
|
34
|
-
"@abtnode/util": "1.17.5-beta-
|
|
35
|
-
"@aigne/aigne-hub": "^0.10.
|
|
36
|
-
"@arcblock/did": "^1.27.
|
|
37
|
-
"@arcblock/did-connect-js": "^1.27.
|
|
38
|
-
"@arcblock/did-ext": "^1.27.
|
|
20
|
+
"@abtnode/analytics": "1.17.5-beta-20251214-231110-497f8d27",
|
|
21
|
+
"@abtnode/auth": "1.17.5-beta-20251214-231110-497f8d27",
|
|
22
|
+
"@abtnode/certificate-manager": "1.17.5-beta-20251214-231110-497f8d27",
|
|
23
|
+
"@abtnode/constant": "1.17.5-beta-20251214-231110-497f8d27",
|
|
24
|
+
"@abtnode/cron": "1.17.5-beta-20251214-231110-497f8d27",
|
|
25
|
+
"@abtnode/db-cache": "1.17.5-beta-20251214-231110-497f8d27",
|
|
26
|
+
"@abtnode/docker-utils": "1.17.5-beta-20251214-231110-497f8d27",
|
|
27
|
+
"@abtnode/logger": "1.17.5-beta-20251214-231110-497f8d27",
|
|
28
|
+
"@abtnode/models": "1.17.5-beta-20251214-231110-497f8d27",
|
|
29
|
+
"@abtnode/queue": "1.17.5-beta-20251214-231110-497f8d27",
|
|
30
|
+
"@abtnode/rbac": "1.17.5-beta-20251214-231110-497f8d27",
|
|
31
|
+
"@abtnode/router-provider": "1.17.5-beta-20251214-231110-497f8d27",
|
|
32
|
+
"@abtnode/static-server": "1.17.5-beta-20251214-231110-497f8d27",
|
|
33
|
+
"@abtnode/timemachine": "1.17.5-beta-20251214-231110-497f8d27",
|
|
34
|
+
"@abtnode/util": "1.17.5-beta-20251214-231110-497f8d27",
|
|
35
|
+
"@aigne/aigne-hub": "^0.10.14",
|
|
36
|
+
"@arcblock/did": "^1.27.14",
|
|
37
|
+
"@arcblock/did-connect-js": "^1.27.14",
|
|
38
|
+
"@arcblock/did-ext": "^1.27.14",
|
|
39
39
|
"@arcblock/did-motif": "^1.1.14",
|
|
40
|
-
"@arcblock/did-util": "^1.27.
|
|
41
|
-
"@arcblock/event-hub": "^1.27.
|
|
42
|
-
"@arcblock/jwt": "^1.27.
|
|
40
|
+
"@arcblock/did-util": "^1.27.14",
|
|
41
|
+
"@arcblock/event-hub": "^1.27.14",
|
|
42
|
+
"@arcblock/jwt": "^1.27.14",
|
|
43
43
|
"@arcblock/pm2-events": "^0.0.5",
|
|
44
|
-
"@arcblock/validator": "^1.27.
|
|
45
|
-
"@arcblock/vc": "^1.27.
|
|
46
|
-
"@blocklet/constant": "1.17.5-beta-
|
|
47
|
-
"@blocklet/did-space-js": "^1.2.
|
|
48
|
-
"@blocklet/env": "1.17.5-beta-
|
|
49
|
-
"@blocklet/error": "^0.3.
|
|
50
|
-
"@blocklet/meta": "1.17.5-beta-
|
|
51
|
-
"@blocklet/resolver": "1.17.5-beta-
|
|
52
|
-
"@blocklet/sdk": "1.17.5-beta-
|
|
53
|
-
"@blocklet/server-js": "1.17.5-beta-
|
|
54
|
-
"@blocklet/store": "1.17.5-beta-
|
|
55
|
-
"@blocklet/theme": "^3.2.
|
|
44
|
+
"@arcblock/validator": "^1.27.14",
|
|
45
|
+
"@arcblock/vc": "^1.27.14",
|
|
46
|
+
"@blocklet/constant": "1.17.5-beta-20251214-231110-497f8d27",
|
|
47
|
+
"@blocklet/did-space-js": "^1.2.9",
|
|
48
|
+
"@blocklet/env": "1.17.5-beta-20251214-231110-497f8d27",
|
|
49
|
+
"@blocklet/error": "^0.3.4",
|
|
50
|
+
"@blocklet/meta": "1.17.5-beta-20251214-231110-497f8d27",
|
|
51
|
+
"@blocklet/resolver": "1.17.5-beta-20251214-231110-497f8d27",
|
|
52
|
+
"@blocklet/sdk": "1.17.5-beta-20251214-231110-497f8d27",
|
|
53
|
+
"@blocklet/server-js": "1.17.5-beta-20251214-231110-497f8d27",
|
|
54
|
+
"@blocklet/store": "1.17.5-beta-20251214-231110-497f8d27",
|
|
55
|
+
"@blocklet/theme": "^3.2.13",
|
|
56
56
|
"@fidm/x509": "^1.2.1",
|
|
57
|
-
"@ocap/mcrypto": "^1.27.
|
|
58
|
-
"@ocap/util": "^1.27.
|
|
59
|
-
"@ocap/wallet": "^1.27.
|
|
57
|
+
"@ocap/mcrypto": "^1.27.14",
|
|
58
|
+
"@ocap/util": "^1.27.14",
|
|
59
|
+
"@ocap/wallet": "^1.27.14",
|
|
60
60
|
"@slack/webhook": "^7.0.6",
|
|
61
61
|
"archiver": "^7.0.1",
|
|
62
62
|
"axios": "^1.7.9",
|
|
@@ -116,5 +116,5 @@
|
|
|
116
116
|
"express": "^4.18.2",
|
|
117
117
|
"unzipper": "^0.10.11"
|
|
118
118
|
},
|
|
119
|
-
"gitHead": "
|
|
119
|
+
"gitHead": "aa782962d97ed5d9a994268621e9a652c995c783"
|
|
120
120
|
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const blueGreenUpdateBlockletStatus = async ({ states, did, status, blueGreenComponentIds }) => {
|
|
2
|
-
const outputBlocklet = {};
|
|
3
|
-
await Promise.all(
|
|
4
|
-
blueGreenComponentIds.map(async (item) => {
|
|
5
|
-
if (!item.componentDids.length) {
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
const res = await states.blocklet.setBlockletStatus(did, status, {
|
|
9
|
-
componentDids: item.componentDids,
|
|
10
|
-
isGreen: item.changeToGreen,
|
|
11
|
-
});
|
|
12
|
-
Object.assign(outputBlocklet, res);
|
|
13
|
-
})
|
|
14
|
-
);
|
|
15
|
-
return outputBlocklet;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
module.exports = { blueGreenUpdateBlockletStatus };
|