feat: added deployment service and component

This commit is contained in:
2025-02-10 09:30:51 +01:00
parent a3803d6f6c
commit ab9d299937
11 changed files with 312 additions and 15 deletions

View File

@ -0,0 +1,8 @@
export interface Deployment {
_id: string;
realm_id: string;
name: string;
owner_groups: string[];
access_groups: string[];
config_templates: string[];
}

View 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>;
}

View File

@ -10,6 +10,8 @@
/>
</div>
<mat-divider></mat-divider>
<!-- Username expansion panel -->
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-icon>account_circle</mat-icon>
@ -20,7 +22,10 @@
<button mat-button>Settings</button>
<button mat-button>Logout</button>
</mat-expansion-panel>
<mat-divider></mat-divider>
<!-- Scan Table -->
<button
mat-button
class="menu-item"
@ -29,11 +34,17 @@
<mat-icon>home</mat-icon>
<span class="menu-text">Data Browser</span>
</button>
<mat-expansion-panel>
<!-- Experiment Control Expansion -->
<mat-expansion-panel
(opened)="panelOpened()"
[hideToggle]="hideExperimentPanel"
>
<mat-expansion-panel-header>
<mat-icon>science</mat-icon>
<span class="menu-text">Experiment Control</span>
</mat-expansion-panel-header>
<button
mat-button
class="menu-item"
@ -48,19 +59,29 @@
>
<span class="menu-text">Admin</span>
</button>
<button mat-button class="menu-item" (click)="openDeploymentDialog()">
<span class="menu-text">{{ selectOrSwitchButtonTitle }}</span>
</button>
</mat-expansion-panel>
<!-- Help -->
<button mat-button class="menu-item">
<mat-icon>help</mat-icon>
<span class="menu-text">Help</span>
</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-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>
</div>
</mat-sidenav-content>

View File

@ -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);
}

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -0,0 +1,8 @@
.deployment-selection-card {
width: 100%;
height: 80%;
}
form {
padding-top: 10px;
}

View File

@ -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();
});
});

View File

@ -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);
}
}

View 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();
});
});

View 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);
}
}