diff --git a/src/Web/StellaOps.Web/src/app/features/releases/releases-unified-page.component.ts b/src/Web/StellaOps.Web/src/app/features/releases/releases-unified-page.component.ts
index 5bc171b70..f09cb37a1 100644
--- a/src/Web/StellaOps.Web/src/app/features/releases/releases-unified-page.component.ts
+++ b/src/Web/StellaOps.Web/src/app/features/releases/releases-unified-page.component.ts
@@ -8,11 +8,15 @@
* Tab 2 "Approvals": embeds the existing ApprovalQueueComponent.
*/
-import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject, signal, computed } from '@angular/core';
+import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, inject, signal, computed } from '@angular/core';
import { PageActionService } from '../../core/services/page-action.service';
import { PageActionOutletComponent } from '../../shared/components/page-action-outlet/page-action-outlet.component';
import { UpperCasePipe, SlicePipe } from '@angular/common';
import { RouterLink, ActivatedRoute } from '@angular/router';
+import { take } from 'rxjs';
+import { APPROVAL_API } from '../../core/api/approval.client';
+import type { ApprovalApi } from '../../core/api/approval.client';
+import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
import { StellaFilterChipComponent, FilterChipOption } from '../../shared/components/stella-filter-chip/stella-filter-chip.component';
import { StellaPageTabsComponent, StellaPageTab } from '../../shared/components/stella-page-tabs/stella-page-tabs.component';
import { PaginationComponent, PageChangeEvent } from '../../shared/components/pagination/pagination.component';
@@ -63,6 +67,7 @@ export interface PipelineRelease {
PaginationComponent,
PageActionOutletComponent,
ReleaseListComponent,
+ ConfirmDialogComponent,
],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@@ -205,10 +210,10 @@ export interface PipelineRelease {
}
@if (r.gatePendingApprovals > 0) {
-
+
+
}
@if (r.gateStatus === 'block') {
@@ -260,6 +265,12 @@ export interface PipelineRelease {
@if (activeTab() === 'versions') {
}
+
+
`,
styles: [`
@@ -380,10 +391,39 @@ export class ReleasesUnifiedPageComponent implements OnInit, OnDestroy {
private readonly store = inject(ReleaseManagementStore);
private readonly context = inject(PlatformContextStore);
private readonly route = inject(ActivatedRoute);
+ private readonly approvalApi = inject(APPROVAL_API);
readonly releaseTabs = RELEASE_TABS;
readonly activeTab = signal(this.route.snapshot.queryParamMap.get('tab') || 'releases');
+ // Approve dialog
+ readonly pendingApproveRelease = signal(null);
+ @ViewChild('approveConfirm') approveConfirmRef!: ConfirmDialogComponent;
+
+ readonly approveMessage = computed(() => {
+ const r = this.pendingApproveRelease();
+ if (!r) return '';
+ return `Approve release "${r.name}" (${r.version || r.digest?.slice(0, 19) || 'unknown'}) for promotion?`;
+ });
+
+ openApproveDialog(r: PipelineRelease): void {
+ this.pendingApproveRelease.set(r);
+ this.approveConfirmRef.open();
+ }
+
+ executeApprove(): void {
+ const r = this.pendingApproveRelease();
+ if (!r) return;
+ // Call the approval decision endpoint for this release
+ this.approvalApi.approve(r.id, `Approved from releases page`).pipe(take(1)).subscribe({
+ next: () => {
+ this.pendingApproveRelease.set(null);
+ this.store.loadReleases({});
+ },
+ error: () => this.pendingApproveRelease.set(null),
+ });
+ }
+
ngOnInit(): void {
this.pageAction.set({ label: 'New Release', route: '/releases/new' });
this.context.initialize();