From ab9d2999370ea1922219d30f2bea7acd8355a309 Mon Sep 17 00:00:00 2001 From: wakonig_k Date: Mon, 10 Feb 2025 09:30:51 +0100 Subject: [PATCH] feat: added deployment service and component --- .../src/app/core/model/deployment.ts | 8 ++ .../bec_atlas/src/app/core/model/realm.ts | 10 +++ .../app/dashboard/dashboard.component.html | 31 ++++++-- .../app/dashboard/dashboard.component.scss | 15 ++++ .../src/app/dashboard/dashboard.component.ts | 69 ++++++++++++++--- .../deployment-selection.component.html | 34 +++++++++ .../deployment-selection.component.scss | 8 ++ .../deployment-selection.component.spec.ts | 23 ++++++ .../deployment-selection.component.ts | 76 +++++++++++++++++++ .../src/app/deployment.service.spec.ts | 16 ++++ .../bec_atlas/src/app/deployment.service.ts | 37 +++++++++ 11 files changed, 312 insertions(+), 15 deletions(-) create mode 100644 frontend/bec_atlas/src/app/core/model/deployment.ts create mode 100644 frontend/bec_atlas/src/app/core/model/realm.ts create mode 100644 frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.html create mode 100644 frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.scss create mode 100644 frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.spec.ts create mode 100644 frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.ts create mode 100644 frontend/bec_atlas/src/app/deployment.service.spec.ts create mode 100644 frontend/bec_atlas/src/app/deployment.service.ts diff --git a/frontend/bec_atlas/src/app/core/model/deployment.ts b/frontend/bec_atlas/src/app/core/model/deployment.ts new file mode 100644 index 0000000..82737f6 --- /dev/null +++ b/frontend/bec_atlas/src/app/core/model/deployment.ts @@ -0,0 +1,8 @@ +export interface Deployment { + _id: string; + realm_id: string; + name: string; + owner_groups: string[]; + access_groups: string[]; + config_templates: string[]; +} diff --git a/frontend/bec_atlas/src/app/core/model/realm.ts b/frontend/bec_atlas/src/app/core/model/realm.ts new file mode 100644 index 0000000..5e746a1 --- /dev/null +++ b/frontend/bec_atlas/src/app/core/model/realm.ts @@ -0,0 +1,10 @@ +import { Deployment } from './deployment'; + +export interface Realm { + _id: string; + realm_id: string; + name: string; + owner_groups: Array; + access_groups: Array; + deployments: Array; +} diff --git a/frontend/bec_atlas/src/app/dashboard/dashboard.component.html b/frontend/bec_atlas/src/app/dashboard/dashboard.component.html index a6604e4..5d6c72a 100644 --- a/frontend/bec_atlas/src/app/dashboard/dashboard.component.html +++ b/frontend/bec_atlas/src/app/dashboard/dashboard.component.html @@ -10,6 +10,8 @@ /> + + account_circle @@ -20,7 +22,10 @@ + + + - + + + science Experiment Control + + + + + +
+ @if ((realm_name) && (deployment_name)) { + + }
- -
diff --git a/frontend/bec_atlas/src/app/dashboard/dashboard.component.scss b/frontend/bec_atlas/src/app/dashboard/dashboard.component.scss index 6ee1438..6c16426 100644 --- a/frontend/bec_atlas/src/app/dashboard/dashboard.component.scss +++ b/frontend/bec_atlas/src/app/dashboard/dashboard.component.scss @@ -15,6 +15,12 @@ mat-sidenav { @include mat.elevation(3); } +::ng-deep .mat-drawer-inner-container { + display: flex; + flex-direction: column; + height: 100%; /* Ensure it takes full height */ +} + .sidenav.collapsed { width: 60px; } @@ -88,3 +94,12 @@ mat-sidenav-content { background-color: var(--mat-sys-surface-dim); height: 100vh; } + +.spacer { + flex-grow: 1; /* Pushes everything below it down */ +} + +.footer { + padding-bottom: 10px; + color: var(--mat-sys-primary); +} diff --git a/frontend/bec_atlas/src/app/dashboard/dashboard.component.ts b/frontend/bec_atlas/src/app/dashboard/dashboard.component.ts index 03fc49a..ef8c26e 100644 --- a/frontend/bec_atlas/src/app/dashboard/dashboard.component.ts +++ b/frontend/bec_atlas/src/app/dashboard/dashboard.component.ts @@ -1,43 +1,92 @@ -import { Component } from '@angular/core'; -import { DeviceBoxComponent } from '../device-box/device-box.component'; -import { QueueTableComponent } from '../queue-table/queue-table.component'; +import { Component, inject } from '@angular/core'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatIconModule } from '@angular/material/icon'; -import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { BreakpointObserver } from '@angular/cdk/layout'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; -import { ScanTableComponent } from '../scan-table/scan-table.component'; import { MatDividerModule } from '@angular/material/divider'; import { RouterModule } from '@angular/router'; import { MatExpansionModule } from '@angular/material/expansion'; - +import { DeploymentService } from '../deployment.service'; +import { + MatDialog, + MatDialogModule, + MatDialogConfig, +} from '@angular/material/dialog'; +import { DeploymentSelectionComponent } from '../deployment-selection/deployment-selection.component'; +import { RedisConnectorService } from '../core/redis-connector.service'; @Component({ selector: 'app-dashboard', imports: [ - DeviceBoxComponent, CommonModule, - QueueTableComponent, MatExpansionModule, MatDividerModule, MatSidenavModule, MatIconModule, MatButtonModule, - ScanTableComponent, + MatDialogModule, RouterModule, ], + providers: [DeploymentService, RedisConnectorService], templateUrl: './dashboard.component.html', styleUrl: './dashboard.component.scss', }) export class DashboardComponent { // isScreenSmall = false; + readonly dialog = inject(MatDialog); + deployment_name: string = ''; + realm_name: string = ''; + hideExperimentPanel = true; + selectOrSwitchButtonTitle = 'Select Deployment'; - constructor(private breakpointObserver: BreakpointObserver) {} + constructor( + private breakpointObserver: BreakpointObserver, + private deploymentService: DeploymentService + ) {} ngOnInit(): void { + // check if the user has already selected a deployment + // if not, open the deployment selection dialog + this.deploymentService.selectedDeployment.subscribe((deployment) => { + if (deployment) { + console.log('Updating deployment name to: ', deployment.name); + this.deployment_name = deployment.name; + this.realm_name = deployment.realm_id; + this.hideExperimentPanel = false; + this.selectOrSwitchButtonTitle = 'Switch Deployment'; + } else { + this.deployment_name = ''; + this.realm_name = ''; + this.hideExperimentPanel = true; + this.selectOrSwitchButtonTitle = 'Select Deployment'; + } + }); + // this.breakpointObserver // .observe([Breakpoints.Small, Breakpoints.XSmall]) // .subscribe((result) => { // this.isScreenSmall = result.matches; // }); } + + openDeploymentDialog() { + // open deployment dialog + let dialogConfig: MatDialogConfig = { + disableClose: true, + width: '80%', + }; + let dialogRef = this.dialog.open( + DeploymentSelectionComponent, + dialogConfig + ); + dialogRef.afterClosed().subscribe((result) => { + this.deploymentService.selectDeployment(result); + }); + } + + panelOpened() { + if (!this.deploymentService.selectedDeployment.value) { + this.openDeploymentDialog(); + } + } } diff --git a/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.html b/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.html new file mode 100644 index 0000000..1dd4067 --- /dev/null +++ b/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.html @@ -0,0 +1,34 @@ + +

Deployments

+
+
+ + Select Beamline + + + {{ beamline.name }} + + + + + + Select Deployment + + + {{ deployment.name }} + + + +
+
+ + + + +
diff --git a/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.scss b/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.scss new file mode 100644 index 0000000..d7812fe --- /dev/null +++ b/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.scss @@ -0,0 +1,8 @@ +.deployment-selection-card { + width: 100%; + height: 80%; +} + +form { + padding-top: 10px; +} diff --git a/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.spec.ts b/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.spec.ts new file mode 100644 index 0000000..2380d32 --- /dev/null +++ b/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeploymentSelectionComponent } from './deployment-selection.component'; + +describe('DeploymentSelectionComponent', () => { + let component: DeploymentSelectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DeploymentSelectionComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DeploymentSelectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.ts b/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.ts new file mode 100644 index 0000000..931b43c --- /dev/null +++ b/frontend/bec_atlas/src/app/deployment-selection/deployment-selection.component.ts @@ -0,0 +1,76 @@ +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { RealmDataService } from '../core/remote-data.service'; +import { Realm } from '../core/model/realm'; +import { Deployment } from '../core/model/deployment'; + +@Component({ + selector: 'app-deployment-selection', + imports: [ + MatCardModule, + MatButtonModule, + MatDialogModule, + MatFormFieldModule, + MatSelectModule, + ReactiveFormsModule, + CommonModule, + ], + templateUrl: './deployment-selection.component.html', + styleUrl: './deployment-selection.component.scss', +}) +export class DeploymentSelectionComponent { + form: FormGroup; + beamlines: Realm[] = []; + deployments: Deployment[] = []; + selectedDeployment: Deployment | null = null; + + constructor( + private fb: FormBuilder, + private realmDataService: RealmDataService, + private dialogRef: MatDialogRef + ) { + this.form = this.fb.group({ + beamline: [''], + deployment: [''], + }); + } + + ngOnInit(): void { + // fetch deployments + this.realmDataService + .getRealmsWithDeploymentAccess(true) + .subscribe((realms) => { + console.log(realms); + this.beamlines = realms; + // this.deployments = deployments; + }); + this.form.get('beamline')?.valueChanges.subscribe((beamlineId) => { + if (beamlineId) { + let beamline = this.beamlines.find((realm) => realm._id === beamlineId); + this.deployments = beamline?.deployments ?? []; + } + }); + this.form.get('deployment')?.valueChanges.subscribe((deploymentId) => { + if (deploymentId) { + let selectedDeployment = this.deployments.find( + (deployment) => deployment._id === deploymentId + ); + if (selectedDeployment) { + this.selectedDeployment = selectedDeployment; + } else { + this.selectedDeployment = null; + } + } + }); + } + + applySelection() { + this.dialogRef.close(this.selectedDeployment); + } +} diff --git a/frontend/bec_atlas/src/app/deployment.service.spec.ts b/frontend/bec_atlas/src/app/deployment.service.spec.ts new file mode 100644 index 0000000..ac55044 --- /dev/null +++ b/frontend/bec_atlas/src/app/deployment.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { DeploymentService } from './deployment.service'; + +describe('DeploymentService', () => { + let service: DeploymentService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DeploymentService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/bec_atlas/src/app/deployment.service.ts b/frontend/bec_atlas/src/app/deployment.service.ts new file mode 100644 index 0000000..47615c5 --- /dev/null +++ b/frontend/bec_atlas/src/app/deployment.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { DeploymentDataService } from './core/remote-data.service'; +import { Deployment } from './core/model/deployment'; + +@Injectable() +export class DeploymentService { + selectedDeployment = new BehaviorSubject(null); + + constructor(private deploymentDataService: DeploymentDataService) { + // check the local storage for a selected deployment + const deployment = sessionStorage.getItem('selected_deployment'); + if (!deployment) { + return; + } + this.deploymentDataService + .getDeployment(deployment) + .subscribe((deployment) => { + if (deployment) { + this.selectedDeployment.next(deployment); + } + }); + } + + selectDeployment(deployment: Deployment | null): void { + // save the selected deployment to local storage + if (!deployment) { + sessionStorage.removeItem('selected_deployment'); + this.selectedDeployment.next(null); + return; + } + + sessionStorage.setItem('selected_deployment', deployment._id); + + this.selectedDeployment.next(deployment); + } +}