@abtnode/core 1.17.5-beta-20251209-090953-3a59e7ac → 1.17.5-beta-20251214-122206-29056e8c
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 +51 -40
- package/lib/blocklet/manager/helper/blue-green-get-componentids.js +11 -16
- package/lib/blocklet/manager/helper/blue-green-start-blocklet.js +118 -82
- package/lib/blocklet/migration-dist/migration.cjs +1 -1
- package/lib/migrations/index.js +4 -4
- package/lib/monitor/blocklet-runtime-monitor.js +3 -5
- package/lib/states/audit-log.js +34 -9
- package/lib/states/blocklet-child.js +193 -0
- package/lib/states/blocklet-extras.js +63 -1
- package/lib/states/blocklet.js +292 -11
- package/lib/states/index.js +4 -1
- package/lib/states/notification.js +4 -2
- package/lib/util/blocklet.js +112 -42
- package/lib/util/migration-sqlite-to-postgres.js +240 -6
- 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
|
*
|
|
@@ -905,6 +893,45 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
905
893
|
this.emit(BlockletEvents.statusChange, doc1);
|
|
906
894
|
const startedBlockletDids = [];
|
|
907
895
|
const errorBlockletDids = [];
|
|
896
|
+
const parentBlockletId = blocklet.id;
|
|
897
|
+
|
|
898
|
+
// Helper function to update child status immediately and emit events
|
|
899
|
+
const updateChildStatusImmediately = async (componentDid, status) => {
|
|
900
|
+
if (!states.blockletChild || !parentBlockletId) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
try {
|
|
905
|
+
await states.blockletChild.updateChildStatus(parentBlockletId, componentDid, {
|
|
906
|
+
status,
|
|
907
|
+
operator,
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// Get updated blocklet to emit events
|
|
911
|
+
const updatedBlocklet = await this.getBlocklet(did);
|
|
912
|
+
const componentsInfo = getComponentsInternalInfo(updatedBlocklet);
|
|
913
|
+
|
|
914
|
+
this.emit(BlockletInternalEvents.componentUpdated, {
|
|
915
|
+
appDid: blocklet.appDid,
|
|
916
|
+
components: componentsInfo.filter((c) => c.did === componentDid),
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
if (status === BlockletStatus.running) {
|
|
920
|
+
this.emit(BlockletInternalEvents.componentStarted, {
|
|
921
|
+
appDid: blocklet.appDid,
|
|
922
|
+
components: [{ did: componentDid }],
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
this.emit(BlockletEvents.statusChange, updatedBlocklet);
|
|
927
|
+
} catch (err) {
|
|
928
|
+
logger.error('Failed to update child status immediately', {
|
|
929
|
+
componentDid,
|
|
930
|
+
status,
|
|
931
|
+
error: err.message,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
};
|
|
908
935
|
|
|
909
936
|
const notStartedComponentDids = await this.startRequiredComponents({
|
|
910
937
|
componentDids,
|
|
@@ -915,11 +942,13 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
915
942
|
e2eMode,
|
|
916
943
|
context,
|
|
917
944
|
atomic,
|
|
918
|
-
onStarted: (subDid) => {
|
|
945
|
+
onStarted: async (subDid) => {
|
|
919
946
|
startedBlockletDids.push({ did: subDid });
|
|
947
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.running);
|
|
920
948
|
},
|
|
921
|
-
onError: (subDid, error) => {
|
|
949
|
+
onError: async (subDid, error) => {
|
|
922
950
|
errorBlockletDids.push({ did: subDid, error });
|
|
951
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.error);
|
|
923
952
|
},
|
|
924
953
|
});
|
|
925
954
|
|
|
@@ -933,11 +962,13 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
933
962
|
e2eMode,
|
|
934
963
|
componentDids: [componentDid],
|
|
935
964
|
operator,
|
|
936
|
-
onStarted: (subDid) => {
|
|
965
|
+
onStarted: async (subDid) => {
|
|
937
966
|
startedBlockletDids.push({ did: subDid });
|
|
967
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.running);
|
|
938
968
|
},
|
|
939
|
-
onError: (subDid, error) => {
|
|
969
|
+
onError: async (subDid, error) => {
|
|
940
970
|
errorBlockletDids.push({ did: subDid, error });
|
|
971
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.error);
|
|
941
972
|
},
|
|
942
973
|
},
|
|
943
974
|
context
|
|
@@ -950,15 +981,12 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
950
981
|
let errorDescription = '';
|
|
951
982
|
let resultBlocklet = nextBlocklet;
|
|
952
983
|
|
|
984
|
+
// Status updates are now done immediately in callbacks, so we only need to handle final events and cleanup
|
|
953
985
|
if (startedBlockletDids.length) {
|
|
954
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
955
|
-
componentDids: startedBlockletDids.map((x) => x.did),
|
|
956
|
-
operator,
|
|
957
|
-
});
|
|
958
|
-
|
|
959
986
|
const finalBlocklet = await this.getBlocklet(did);
|
|
960
987
|
resultBlocklet = finalBlocklet;
|
|
961
988
|
|
|
989
|
+
// Sync app config after all components started
|
|
962
990
|
await this.configSynchronizer.throttledSyncAppConfig(finalBlocklet, { wait: 200 });
|
|
963
991
|
const componentsInfo = getComponentsInternalInfo(finalBlocklet);
|
|
964
992
|
this.emit(BlockletInternalEvents.componentUpdated, {
|
|
@@ -1009,10 +1037,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
1009
1037
|
componentDids: errorBlockletDids.map((x) => x.did),
|
|
1010
1038
|
shouldUpdateBlockletStatus: false,
|
|
1011
1039
|
});
|
|
1012
|
-
|
|
1013
|
-
componentDids: errorBlockletDids.map((x) => x.did),
|
|
1014
|
-
operator,
|
|
1015
|
-
});
|
|
1040
|
+
|
|
1016
1041
|
const finalBlocklet = await this.getBlocklet(did);
|
|
1017
1042
|
resultBlocklet = finalBlocklet;
|
|
1018
1043
|
this.emit(BlockletEvents.startFailed, {
|
|
@@ -3413,7 +3438,7 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
3413
3438
|
return x;
|
|
3414
3439
|
});
|
|
3415
3440
|
|
|
3416
|
-
const blueGreenComponentIds =
|
|
3441
|
+
const blueGreenComponentIds = blueGreenGetComponentIds(oldBlocklet || blocklet, componentDids);
|
|
3417
3442
|
|
|
3418
3443
|
try {
|
|
3419
3444
|
if (process.env.ABT_NODE_DISABLE_BLUE_GREEN === 'true' || process.env.ABT_NODE_DISABLE_BLUE_GREEN === '1') {
|
|
@@ -4926,20 +4951,6 @@ class DiskBlockletManager extends BaseBlockletManager {
|
|
|
4926
4951
|
});
|
|
4927
4952
|
}
|
|
4928
4953
|
|
|
4929
|
-
async _syncPm2Status(pm2Status, did, componentDid) {
|
|
4930
|
-
try {
|
|
4931
|
-
const blocklet = await states.blocklet.getBlocklet(did);
|
|
4932
|
-
const component = findComponentByIdV2(blocklet, componentDid);
|
|
4933
|
-
if (component && util.shouldUpdateBlockletStatus(component.status)) {
|
|
4934
|
-
const newStatus = pm2StatusMap[pm2Status];
|
|
4935
|
-
await states.blocklet.setBlockletStatus(did, newStatus, { componentDids: [componentDid] });
|
|
4936
|
-
logger.info('sync pm2 status to blocklet', { did, componentDid, pm2Status, newStatus });
|
|
4937
|
-
}
|
|
4938
|
-
} catch (error) {
|
|
4939
|
-
logger.error('sync pm2 status to blocklet failed', { did, componentDid, pm2Status, error });
|
|
4940
|
-
}
|
|
4941
|
-
}
|
|
4942
|
-
|
|
4943
4954
|
/**
|
|
4944
4955
|
* @param {string} action install, upgrade, installComponent, upgradeComponent
|
|
4945
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,10 +163,94 @@ 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 = [];
|
|
170
|
+
const appId = blocklet1.id;
|
|
171
|
+
|
|
172
|
+
const notificationChange = async () => {
|
|
173
|
+
// Get latest children from blocklet_children table instead of reloading entire blocklet
|
|
174
|
+
const latestChildren = await states.blocklet.loadChildren(appId);
|
|
175
|
+
|
|
176
|
+
// Merge latest children into blocklet1 for event emission
|
|
177
|
+
const finalBlocklet = {
|
|
178
|
+
...blocklet1,
|
|
179
|
+
children: latestChildren,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
await manager.configSynchronizer.throttledSyncAppConfig(finalBlocklet);
|
|
183
|
+
const componentsInfo = getComponentsInternalInfo(finalBlocklet);
|
|
184
|
+
manager.emit(BlockletInternalEvents.componentUpdated, {
|
|
185
|
+
appDid: blocklet1.appDid,
|
|
186
|
+
components: componentsInfo,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
manager.emit(BlockletInternalEvents.componentStarted, {
|
|
190
|
+
appDid: blocklet1.appDid,
|
|
191
|
+
components: startedBlockletDids.map((x) => ({ did: x.did })),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Emit statusChange event so UI can see the status change
|
|
195
|
+
manager.emit(BlockletEvents.statusChange, finalBlocklet);
|
|
196
|
+
|
|
197
|
+
manager.emit(BlockletEvents.started, { ...finalBlocklet, componentDids: startedBlockletDids.map((x) => x.did) });
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Helper function to update child status immediately and emit events
|
|
201
|
+
const updateChildStatusImmediately = async (componentDid, status, isGreen = false) => {
|
|
202
|
+
if (!states.blockletChild || !appId) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const updates = {
|
|
208
|
+
operator,
|
|
209
|
+
inProgressStart: Date.now(),
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (status === BlockletStatus.running) {
|
|
213
|
+
await states.blockletChild.updateChildStatusRunning(appId, componentDid, isGreen, updates);
|
|
214
|
+
} else {
|
|
215
|
+
await states.blockletChild.updateChildStatusError(appId, componentDid, isGreen, updates);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Get latest children from blocklet_children table and emit events immediately
|
|
219
|
+
const latestChildren = await states.blocklet.loadChildren(appId);
|
|
220
|
+
const updatedBlocklet = {
|
|
221
|
+
...blocklet1,
|
|
222
|
+
children: latestChildren,
|
|
223
|
+
};
|
|
224
|
+
const componentsInfo = getComponentsInternalInfo(updatedBlocklet);
|
|
225
|
+
|
|
226
|
+
manager.emit(BlockletInternalEvents.componentUpdated, {
|
|
227
|
+
appDid: blocklet1.appDid,
|
|
228
|
+
components: componentsInfo.filter((c) => c.did === componentDid),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (status === BlockletStatus.running) {
|
|
232
|
+
manager.emit(BlockletInternalEvents.componentStarted, {
|
|
233
|
+
appDid: blocklet1.appDid,
|
|
234
|
+
components: [{ did: componentDid }],
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
manager.emit(BlockletEvents.statusChange, updatedBlocklet);
|
|
239
|
+
|
|
240
|
+
// Emit statusChange event immediately so UI can see each component's status change
|
|
241
|
+
manager.emit(BlockletEvents.started, {
|
|
242
|
+
...updatedBlocklet,
|
|
243
|
+
componentDids: startedBlockletDids.map((x) => x.did),
|
|
244
|
+
});
|
|
245
|
+
} catch (err) {
|
|
246
|
+
logger.error('Failed to update child status immediately', {
|
|
247
|
+
componentDid,
|
|
248
|
+
status,
|
|
249
|
+
isGreen,
|
|
250
|
+
error: err.message,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
};
|
|
176
254
|
|
|
177
255
|
for (const item of blueGreenComponentIds) {
|
|
178
256
|
if (!item.componentDids.length) {
|
|
@@ -194,6 +272,14 @@ const blueGreenStartBlocklet = async (
|
|
|
194
272
|
});
|
|
195
273
|
}
|
|
196
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
|
+
|
|
197
283
|
const nextBlocklet = await manager.ensureBlocklet(did, { e2eMode });
|
|
198
284
|
|
|
199
285
|
manager.emit(BlockletEvents.statusChange, nextBlocklet);
|
|
@@ -274,6 +360,16 @@ const blueGreenStartBlocklet = async (
|
|
|
274
360
|
// 收集成功的组件(排除已经在 errorBlockletDids 中的组件)
|
|
275
361
|
startedBlockletDids.push({ did: subDid, isGreen: item.changeToGreen });
|
|
276
362
|
|
|
363
|
+
// Update status immediately
|
|
364
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.running, item.changeToGreen);
|
|
365
|
+
|
|
366
|
+
await manager.deleteProcess({
|
|
367
|
+
did,
|
|
368
|
+
componentDids: [subDid],
|
|
369
|
+
isGreen: !item.changeToGreen,
|
|
370
|
+
shouldUpdateBlockletStatus: false,
|
|
371
|
+
});
|
|
372
|
+
|
|
277
373
|
logger.info('Green environment started successfully', {
|
|
278
374
|
did,
|
|
279
375
|
componentDids: [subDid],
|
|
@@ -285,8 +381,16 @@ const blueGreenStartBlocklet = async (
|
|
|
285
381
|
// 收集失败的组件
|
|
286
382
|
errorBlockletDids.push({ did: subDid, error, isGreen: item.changeToGreen });
|
|
287
383
|
|
|
384
|
+
// Update status immediately
|
|
385
|
+
await updateChildStatusImmediately(subDid, BlockletStatus.error, item.changeToGreen);
|
|
386
|
+
|
|
288
387
|
try {
|
|
289
|
-
await manager.deleteProcess({
|
|
388
|
+
await manager.deleteProcess({
|
|
389
|
+
did,
|
|
390
|
+
componentDids: [subDid],
|
|
391
|
+
isGreen: item.changeToGreen,
|
|
392
|
+
shouldUpdateBlockletStatus: false,
|
|
393
|
+
});
|
|
290
394
|
} catch (cleanupError) {
|
|
291
395
|
logger.error('Failed to cleanup green environment', { cleanupError });
|
|
292
396
|
}
|
|
@@ -297,7 +401,7 @@ const blueGreenStartBlocklet = async (
|
|
|
297
401
|
|
|
298
402
|
await pAll(tasks, { concurrency: 6 });
|
|
299
403
|
|
|
300
|
-
const lastBlocklet = await manager.
|
|
404
|
+
const lastBlocklet = await manager.getBlocklet(did, { e2eMode });
|
|
301
405
|
let errorDescription = '';
|
|
302
406
|
|
|
303
407
|
// 处理启动失败的组件
|
|
@@ -326,37 +430,6 @@ const blueGreenStartBlocklet = async (
|
|
|
326
430
|
});
|
|
327
431
|
}
|
|
328
432
|
|
|
329
|
-
const greenBlockletDids = errorBlockletDids.filter((x) => x.isGreen).map((x) => x.did);
|
|
330
|
-
const blueBlockletDids = errorBlockletDids.filter((x) => !x.isGreen).map((x) => x.did);
|
|
331
|
-
|
|
332
|
-
if (greenBlockletDids.length) {
|
|
333
|
-
await manager.deleteProcess({
|
|
334
|
-
did,
|
|
335
|
-
componentDids: greenBlockletDids,
|
|
336
|
-
shouldUpdateBlockletStatus: false,
|
|
337
|
-
isGreen: true,
|
|
338
|
-
});
|
|
339
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
340
|
-
componentDids: greenBlockletDids,
|
|
341
|
-
operator,
|
|
342
|
-
isGreen: true,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (blueBlockletDids.length) {
|
|
347
|
-
await manager.deleteProcess({
|
|
348
|
-
did,
|
|
349
|
-
componentDids: blueBlockletDids,
|
|
350
|
-
shouldUpdateBlockletStatus: false,
|
|
351
|
-
isGreen: false,
|
|
352
|
-
});
|
|
353
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.error, {
|
|
354
|
-
componentDids: blueBlockletDids,
|
|
355
|
-
operator,
|
|
356
|
-
isGreen: false,
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
|
|
360
433
|
const finalBlocklet = await manager.getBlocklet(did);
|
|
361
434
|
manager.emit(BlockletEvents.startFailed, {
|
|
362
435
|
...finalBlocklet,
|
|
@@ -368,51 +441,14 @@ const blueGreenStartBlocklet = async (
|
|
|
368
441
|
|
|
369
442
|
// 处理成功启动的组件
|
|
370
443
|
if (startedBlockletDids.length) {
|
|
371
|
-
|
|
372
|
-
const startedBlueBlockletDids = startedBlockletDids.filter((x) => !x.isGreen).map((x) => x.did);
|
|
373
|
-
|
|
374
|
-
if (startedGreenBlockletDids.length) {
|
|
375
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
376
|
-
componentDids: startedGreenBlockletDids,
|
|
377
|
-
operator,
|
|
378
|
-
isGreen: true,
|
|
379
|
-
});
|
|
380
|
-
await manager.deleteProcess({ did, componentDids: startedGreenBlockletDids, isGreen: false });
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (startedBlueBlockletDids.length) {
|
|
384
|
-
await states.blocklet.setBlockletStatus(did, BlockletStatus.running, {
|
|
385
|
-
componentDids: startedBlueBlockletDids,
|
|
386
|
-
operator,
|
|
387
|
-
isGreen: false,
|
|
388
|
-
});
|
|
389
|
-
await manager.deleteProcess({ did, componentDids: startedBlueBlockletDids, isGreen: true });
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const finalBlocklet = await manager.getBlocklet(did);
|
|
393
|
-
|
|
394
|
-
await manager.configSynchronizer.throttledSyncAppConfig(finalBlocklet);
|
|
395
|
-
const componentsInfo = getComponentsInternalInfo(finalBlocklet);
|
|
396
|
-
manager.emit(BlockletInternalEvents.componentUpdated, {
|
|
397
|
-
appDid: blocklet1.appDid,
|
|
398
|
-
components: componentsInfo,
|
|
399
|
-
});
|
|
400
|
-
manager.emit(BlockletInternalEvents.componentStarted, {
|
|
401
|
-
appDid: blocklet1.appDid,
|
|
402
|
-
components: startedBlockletDids.map((x) => ({ did: x.did })),
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
manager.emit(BlockletEvents.statusChange, finalBlocklet);
|
|
406
|
-
manager.emit(BlockletEvents.started, {
|
|
407
|
-
...finalBlocklet,
|
|
408
|
-
componentDids: startedBlockletDids.map((x) => x.did),
|
|
409
|
-
});
|
|
444
|
+
await notificationChange();
|
|
410
445
|
}
|
|
411
446
|
|
|
412
447
|
// 根据情况更新 route table, 会判断只有包含多 interfaces 的 DID 才会更新 route table
|
|
413
448
|
if (!['true', '1'].includes(process.env.ABT_NODE_DISABLE_BLUE_GREEN)) {
|
|
449
|
+
const latestBlocklet = await manager.getBlocklet(did, { e2eMode });
|
|
414
450
|
manager.emit(BlockletEvents.blurOrGreenStarted, {
|
|
415
|
-
blocklet:
|
|
451
|
+
blocklet: latestBlocklet,
|
|
416
452
|
componentDids,
|
|
417
453
|
context,
|
|
418
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/migrations/index.js
CHANGED
|
@@ -169,11 +169,11 @@ const runSchemaMigrations = async ({
|
|
|
169
169
|
|
|
170
170
|
// migrate server schema
|
|
171
171
|
dbPaths.server = getDbFilePath(path.join(dataDir, 'core/server.db'));
|
|
172
|
-
await doSchemaMigration(dbPaths.server, 'server');
|
|
172
|
+
await doSchemaMigration(dbPaths.server, 'server', true);
|
|
173
173
|
printSuccess(`Server schema successfully migrated: ${dbPaths.server}`);
|
|
174
174
|
// migrate service schema
|
|
175
175
|
dbPaths.service = getDbFilePath(path.join(dataDir, 'services/service.db'));
|
|
176
|
-
await doSchemaMigration(dbPaths.service, 'service');
|
|
176
|
+
await doSchemaMigration(dbPaths.service, 'service', true);
|
|
177
177
|
printSuccess(`Service schema successfully migrated: ${dbPaths.service}`);
|
|
178
178
|
|
|
179
179
|
// migrate blocklet schema
|
|
@@ -183,7 +183,7 @@ const runSchemaMigrations = async ({
|
|
|
183
183
|
if (env) {
|
|
184
184
|
const filePath = getDbFilePath(path.join(env.value, 'blocklet.db'));
|
|
185
185
|
dbPaths.blocklets.push(filePath);
|
|
186
|
-
await doSchemaMigration(filePath, 'blocklet');
|
|
186
|
+
await doSchemaMigration(filePath, 'blocklet', true);
|
|
187
187
|
printSuccess(`Blocklet schema successfully migrated: ${blocklet.appPid}: ${filePath}`);
|
|
188
188
|
} else {
|
|
189
189
|
printInfo(`Skip migrate schema for blocklet: ${blocklet.appPid}`);
|
|
@@ -193,7 +193,7 @@ const runSchemaMigrations = async ({
|
|
|
193
193
|
// migrate certificate manager schema
|
|
194
194
|
for (let i = 0; i < MODULES.length; i++) {
|
|
195
195
|
const filePath = getDbFilePath(path.join(dataDir, `modules/${MODULES[i]}/module.db`));
|
|
196
|
-
await doSchemaMigration(filePath, MODULES[i]);
|
|
196
|
+
await doSchemaMigration(filePath, MODULES[i], true);
|
|
197
197
|
dbPaths.certificateManagers.push(filePath);
|
|
198
198
|
printSuccess(`${MODULES[i]} schema successfully migrated: ${filePath}`);
|
|
199
199
|
}
|
|
@@ -230,16 +230,14 @@ class BlockletRuntimeMonitor extends EventEmitter {
|
|
|
230
230
|
|
|
231
231
|
insertThrottleMap.set(blockletDid, now);
|
|
232
232
|
|
|
233
|
-
return this.states.runtimeInsight.insert({ did: blockletDid, ...value }).catch((
|
|
233
|
+
return this.states.runtimeInsight.insert({ did: blockletDid, ...value }).catch((error) => {
|
|
234
|
+
const err = typeof error === 'string' ? error : error.message;
|
|
234
235
|
if (err.includes('duplicate key value violates unique constraint ')) {
|
|
235
236
|
console.error('RuntimeInsight insert error duplicate key value violates unique constraint');
|
|
236
237
|
return;
|
|
237
238
|
}
|
|
238
239
|
if (err.name === 'SequelizeValidationError') {
|
|
239
|
-
console.error(
|
|
240
|
-
'RuntimeInsight validation error',
|
|
241
|
-
err.errors.map((e) => e.message)
|
|
242
|
-
);
|
|
240
|
+
console.error('RuntimeInsight validation error:', err);
|
|
243
241
|
} else {
|
|
244
242
|
console.error('RuntimeInsight insert error', err);
|
|
245
243
|
}
|