diff --git a/src/Web/StellaOps.Web/src/app/features/integrations/advisory-vex-sources/advisory-source-catalog.component.ts b/src/Web/StellaOps.Web/src/app/features/integrations/advisory-vex-sources/advisory-source-catalog.component.ts
index 973f1fa89..90c7710a2 100644
--- a/src/Web/StellaOps.Web/src/app/features/integrations/advisory-vex-sources/advisory-source-catalog.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/integrations/advisory-vex-sources/advisory-source-catalog.component.ts
@@ -14,6 +14,7 @@ import {
SourceStatusItem,
SourceConnectivityResultDto,
} from './source-management.api';
+import { buildMirrorCommands, getAdvisoryVexNavigationExtras } from './advisory-vex-route-helpers';
const CATEGORY_ORDER = [
'Primary',
@@ -83,8 +84,8 @@ interface CategoryGroup {
[class]="'mirror-mode-badge mirror-mode-badge--' + mirrorConfig()!.mode.toLowerCase()">
{{ mirrorConfig()!.mode }}
- Configure Mirror
- Connect to Mirror
+ Configure Mirror
+ Connect to Mirror
@if (isConsumerMode()) {
@@ -1053,7 +1054,15 @@ export class AdvisorySourceCatalogComponent implements OnInit {
}
onCreateMirrorDomain(): void {
- this.router.navigate(['/integrations', 'advisory-vex-sources', 'mirror', 'new']);
+ this.router.navigate(buildMirrorCommands(this.router, 'new'), getAdvisoryVexNavigationExtras());
+ }
+
+ mirrorDashboardLink(): string[] {
+ return buildMirrorCommands(this.router);
+ }
+
+ mirrorClientSetupLink(): string[] {
+ return buildMirrorCommands(this.router, 'client-setup');
}
toggleExpanded(sourceId: string): void {
@@ -1124,19 +1133,65 @@ export class AdvisorySourceCatalogComponent implements OnInit {
onCheckAll(): void {
const items = this.catalog();
- this.checking.set(true);
- this.checkProgress.set({ done: 0, total: items.length });
+ const enabledIds = items
+ .filter((item) => this.isSourceEnabled(item.id))
+ .map((item) => item.id);
- this.api.checkAll().pipe(take(1)).subscribe({
- next: () => {
+ if (enabledIds.length === 0) {
+ return;
+ }
+
+ this.checking.set(true);
+ this.checkProgress.set({ done: 0, total: enabledIds.length });
+
+ // Check sources individually in parallel batches of 6 to avoid gateway timeout.
+ // The batch /check endpoint times out at 30s when checking 40+ sources.
+ const batchSize = 6;
+ let completed = 0;
+
+ const checkNext = (startIndex: number): void => {
+ const batch = enabledIds.slice(startIndex, startIndex + batchSize);
+ if (batch.length === 0) {
this.checking.set(false);
this.reloadStatus();
- },
- error: () => {
- this.checking.set(false);
- this.reloadStatus();
- },
- });
+ return;
+ }
+
+ let batchDone = 0;
+ for (const sourceId of batch) {
+ this.api.checkSource(sourceId).pipe(take(1)).subscribe({
+ next: (result) => {
+ this.statuses.update((current) => {
+ const next = new Map(current);
+ const existing = next.get(sourceId);
+ if (existing) {
+ next.set(sourceId, { ...existing, lastCheck: result });
+ } else {
+ next.set(sourceId, { sourceId, enabled: true, lastCheck: result });
+ }
+ return next;
+ });
+
+ completed++;
+ batchDone++;
+ this.checkProgress.set({ done: completed, total: enabledIds.length });
+ if (batchDone === batch.length) {
+ checkNext(startIndex + batchSize);
+ }
+ },
+ error: () => {
+ completed++;
+ batchDone++;
+ this.checkProgress.set({ done: completed, total: enabledIds.length });
+ if (batchDone === batch.length) {
+ checkNext(startIndex + batchSize);
+ }
+ },
+ });
+ }
+ };
+
+ checkNext(0);
}
onCheckSource(sourceId: string): void {