mirror of
https://github.com/bec-project/bec_atlas.git
synced 2025-07-13 22:51:49 +02:00
feat: added deployment service and component
This commit is contained in:
8
frontend/bec_atlas/src/app/core/model/deployment.ts
Normal file
8
frontend/bec_atlas/src/app/core/model/deployment.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface Deployment {
|
||||||
|
_id: string;
|
||||||
|
realm_id: string;
|
||||||
|
name: string;
|
||||||
|
owner_groups: string[];
|
||||||
|
access_groups: string[];
|
||||||
|
config_templates: string[];
|
||||||
|
}
|
10
frontend/bec_atlas/src/app/core/model/realm.ts
Normal file
10
frontend/bec_atlas/src/app/core/model/realm.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Deployment } from './deployment';
|
||||||
|
|
||||||
|
export interface Realm {
|
||||||
|
_id: string;
|
||||||
|
realm_id: string;
|
||||||
|
name: string;
|
||||||
|
owner_groups: Array<string>;
|
||||||
|
access_groups: Array<string>;
|
||||||
|
deployments: Array<Deployment>;
|
||||||
|
}
|
@ -10,6 +10,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<!-- Username expansion panel -->
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-icon>account_circle</mat-icon>
|
<mat-icon>account_circle</mat-icon>
|
||||||
@ -20,7 +22,10 @@
|
|||||||
<button mat-button>Settings</button>
|
<button mat-button>Settings</button>
|
||||||
<button mat-button>Logout</button>
|
<button mat-button>Logout</button>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<!-- Scan Table -->
|
||||||
<button
|
<button
|
||||||
mat-button
|
mat-button
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
@ -29,11 +34,17 @@
|
|||||||
<mat-icon>home</mat-icon>
|
<mat-icon>home</mat-icon>
|
||||||
<span class="menu-text">Data Browser</span>
|
<span class="menu-text">Data Browser</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-expansion-panel>
|
|
||||||
|
<!-- Experiment Control Expansion -->
|
||||||
|
<mat-expansion-panel
|
||||||
|
(opened)="panelOpened()"
|
||||||
|
[hideToggle]="hideExperimentPanel"
|
||||||
|
>
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-icon>science</mat-icon>
|
<mat-icon>science</mat-icon>
|
||||||
<span class="menu-text">Experiment Control</span>
|
<span class="menu-text">Experiment Control</span>
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
mat-button
|
mat-button
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
@ -48,19 +59,29 @@
|
|||||||
>
|
>
|
||||||
<span class="menu-text">Admin</span>
|
<span class="menu-text">Admin</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button mat-button class="menu-item" (click)="openDeploymentDialog()">
|
||||||
|
<span class="menu-text">{{ selectOrSwitchButtonTitle }}</span>
|
||||||
|
</button>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<!-- Help -->
|
||||||
<button mat-button class="menu-item">
|
<button mat-button class="menu-item">
|
||||||
<mat-icon>help</mat-icon>
|
<mat-icon>help</mat-icon>
|
||||||
<span class="menu-text">Help</span>
|
<span class="menu-text">Help</span>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
@if ((realm_name) && (deployment_name)) {
|
||||||
|
<div class="footer">
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<div class="menu-text">{{ realm_name }}</div>
|
||||||
|
<div class="menu-text">{{ deployment_name }}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
|
|
||||||
<mat-sidenav-content>
|
<mat-sidenav-content>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<!-- <app-device-box [device]="'samx'" [signal_name]="'samx'"></app-device-box>
|
|
||||||
<app-device-box [device]="'samy'" [signal_name]="'samy'"></app-device-box>
|
|
||||||
<app-queue-table></app-queue-table> -->
|
|
||||||
<!-- <app-scan-table></app-scan-table> -->
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</mat-sidenav-content>
|
</mat-sidenav-content>
|
||||||
|
@ -15,6 +15,12 @@ mat-sidenav {
|
|||||||
@include mat.elevation(3);
|
@include mat.elevation(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-drawer-inner-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%; /* Ensure it takes full height */
|
||||||
|
}
|
||||||
|
|
||||||
.sidenav.collapsed {
|
.sidenav.collapsed {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
@ -88,3 +94,12 @@ mat-sidenav-content {
|
|||||||
background-color: var(--mat-sys-surface-dim);
|
background-color: var(--mat-sys-surface-dim);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex-grow: 1; /* Pushes everything below it down */
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
color: var(--mat-sys-primary);
|
||||||
|
}
|
||||||
|
@ -1,43 +1,92 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { DeviceBoxComponent } from '../device-box/device-box.component';
|
|
||||||
import { QueueTableComponent } from '../queue-table/queue-table.component';
|
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
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 { CommonModule } from '@angular/common';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { ScanTableComponent } from '../scan-table/scan-table.component';
|
|
||||||
import { MatDividerModule } from '@angular/material/divider';
|
import { MatDividerModule } from '@angular/material/divider';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { MatExpansionModule } from '@angular/material/expansion';
|
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({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
imports: [
|
imports: [
|
||||||
DeviceBoxComponent,
|
|
||||||
CommonModule,
|
CommonModule,
|
||||||
QueueTableComponent,
|
|
||||||
MatExpansionModule,
|
MatExpansionModule,
|
||||||
MatDividerModule,
|
MatDividerModule,
|
||||||
MatSidenavModule,
|
MatSidenavModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
ScanTableComponent,
|
MatDialogModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
],
|
],
|
||||||
|
providers: [DeploymentService, RedisConnectorService],
|
||||||
templateUrl: './dashboard.component.html',
|
templateUrl: './dashboard.component.html',
|
||||||
styleUrl: './dashboard.component.scss',
|
styleUrl: './dashboard.component.scss',
|
||||||
})
|
})
|
||||||
export class DashboardComponent {
|
export class DashboardComponent {
|
||||||
// isScreenSmall = false;
|
// 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 {
|
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
|
// this.breakpointObserver
|
||||||
// .observe([Breakpoints.Small, Breakpoints.XSmall])
|
// .observe([Breakpoints.Small, Breakpoints.XSmall])
|
||||||
// .subscribe((result) => {
|
// .subscribe((result) => {
|
||||||
// this.isScreenSmall = result.matches;
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<mat-dialog-content>
|
||||||
|
<h2>Deployments</h2>
|
||||||
|
<form [formGroup]="form">
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Select Beamline</mat-label>
|
||||||
|
<mat-select formControlName="beamline">
|
||||||
|
<mat-option *ngFor="let beamline of beamlines" [value]="beamline._id">
|
||||||
|
{{ beamline.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Select Deployment</mat-label>
|
||||||
|
<mat-select
|
||||||
|
formControlName="deployment"
|
||||||
|
[disabled]="!form.get('beamline')?.value"
|
||||||
|
>
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let deployment of deployments"
|
||||||
|
[value]="deployment._id"
|
||||||
|
>
|
||||||
|
{{ deployment.name }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-button mat-dialog-close>Cancel</button>
|
||||||
|
<button mat-button (click)="applySelection()" cdkFocusInitial>Apply</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
</mat-dialog-content>
|
@ -0,0 +1,8 @@
|
|||||||
|
.deployment-selection-card {
|
||||||
|
width: 100%;
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
@ -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<DeploymentSelectionComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DeploymentSelectionComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DeploymentSelectionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -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<DeploymentSelectionComponent>
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
16
frontend/bec_atlas/src/app/deployment.service.spec.ts
Normal file
16
frontend/bec_atlas/src/app/deployment.service.spec.ts
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
});
|
37
frontend/bec_atlas/src/app/deployment.service.ts
Normal file
37
frontend/bec_atlas/src/app/deployment.service.ts
Normal file
@ -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<Deployment | null>(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);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user