diff --git a/src/Web/StellaOps.Web/src/app/features/topology/topology-graph-page.component.ts b/src/Web/StellaOps.Web/src/app/features/topology/topology-graph-page.component.ts index 111d9e5d1..fd0894da5 100644 --- a/src/Web/StellaOps.Web/src/app/features/topology/topology-graph-page.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/topology/topology-graph-page.component.ts @@ -6,8 +6,9 @@ import { inject, signal, } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { RouterLink } from '@angular/router'; -import { catchError, of, take } from 'rxjs'; +import { catchError, map, of, take } from 'rxjs'; import { PlatformContextStore } from '../../core/context/platform-context.store'; import { TopologyLayoutService } from './topology-layout.service'; @@ -17,7 +18,22 @@ import { TopologyPositionedNode, TopologyRoutedEdge, } from './topology-layout.models'; -import { TopologyTarget, TopologyHost } from './topology.models'; +import { TopologyTarget, TopologyHost, PlatformListResponse } from './topology.models'; + +interface ReleaseActivity { + activityId: string; + releaseId: string; + releaseName: string; + version?: string; + status: string; + eventType: string; + occurredAt: string; +} + +const PENDING_STATUSES = new Set([ + 'pending', 'promoting', 'awaiting_approval', 'gates_running', + 'deploying', 'draft', 'ready', +]); @Component({ selector: 'app-topology-graph-page', @@ -128,6 +144,41 @@ import { TopologyTarget, TopologyHost } from './topology.models'; @if (detailTargets().length === 0 && detailHosts().length === 0 && !detailLoading()) {
No hosts or targets registered.
} + + +| Release | Version | Status |
|---|---|---|
| {{ rel.releaseName }} | +{{ rel.version || '—' }} | +{{ rel.status }} | +
No {{ releasesShowDone() ? 'completed' : 'pending' }} releases.
+ } @if (detailLoading()) {Loading...
} @@ -398,6 +449,60 @@ import { TopologyTarget, TopologyHost } from './topology.models'; margin-bottom: 0.15rem; } + /* Section header with toggle */ + .drawer-section-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 0.3rem; + border-top: 1px solid var(--color-border-primary); + } + + .toggle-group { + display: flex; + border: 1px solid var(--color-border-primary); + border-radius: var(--radius-sm); + overflow: hidden; + } + + .toggle-group button { + border: none; + background: var(--color-surface-secondary); + color: var(--color-text-secondary); + font-size: 0.65rem; + padding: 0.15rem 0.4rem; + cursor: pointer; + } + + .toggle-group button + button { + border-left: 1px solid var(--color-border-primary); + } + + .toggle-group .toggle-active { + background: var(--color-brand-primary); + color: var(--color-btn-primary-text, #fff); + } + + .drawer-link { + color: var(--color-text-link); + text-decoration: none; + font-weight: 500; + } + + .drawer-link:hover { + text-decoration: underline; + } + + .release-status { + font-size: 0.65rem; + font-weight: 500; + } + + .release-status--deployed, .release-status--succeeded { color: var(--color-status-success-text); } + .release-status--pending, .release-status--promoting, .release-status--awaiting_approval { color: var(--color-status-warning-text); } + .release-status--failed, .release-status--cancelled, .release-status--rejected { color: var(--color-status-error-text); } + .release-status--draft, .release-status--ready { color: var(--color-text-secondary); } + .drawer-actions { padding-top: 0.3rem; border-top: 1px solid var(--color-border-primary); @@ -425,6 +530,7 @@ import { TopologyTarget, TopologyHost } from './topology.models'; `], }) export class TopologyGraphPageComponent { + private readonly http = inject(HttpClient); private readonly layoutService = inject(TopologyLayoutService); readonly context = inject(PlatformContextStore); @@ -435,7 +541,17 @@ export class TopologyGraphPageComponent { readonly selectedEdge = signal