fix: added missing overview grid and admin components

This commit is contained in:
2025-01-29 12:26:08 +01:00
parent c7ca419f02
commit d375ea2734
8 changed files with 409 additions and 0 deletions

View File

@ -0,0 +1,94 @@
<mat-card class="admin-card">
<mat-card-header>
<mat-card-title>Deployment Access Control</mat-card-title>
</mat-card-header>
<mat-card-content>
<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>
<div class="table-container" *ngIf="form.get('deployment')?.value">
<div class="table-actions">
<button mat-raised-button color="primary" (click)="addUser()">
Add User
</button>
</div>
<table mat-table [dataSource]="aclEntries" class="acl-table">
<!-- Username Column -->
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef>Username</th>
<td mat-cell *matCellDef="let entry">{{ entry.username }}</td>
</ng-container>
<!-- Access Level Column -->
<ng-container matColumnDef="accessLevel">
<th mat-header-cell *matHeaderCellDef>Access Level</th>
<td mat-cell *matCellDef="let entry">
<mat-select
[(value)]="entry.accessLevel"
(selectionChange)="updateAccessLevel(entry.userId, $event.value)"
>
<mat-option value="admin">Admin</mat-option>
<mat-option value="user">User</mat-option>
<mat-option value="viewer">Viewer</mat-option>
</mat-select>
</td>
</ng-container>
<!-- Last Modified Column -->
<ng-container matColumnDef="lastModified">
<th mat-header-cell *matHeaderCellDef>Last Modified</th>
<td mat-cell *matCellDef="let entry">
{{ entry.lastModified | date : "short" }}
</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>Actions</th>
<td mat-cell *matCellDef="let entry">
<button
mat-icon-button
color="warn"
(click)="removeUser(entry.userId)"
>
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,39 @@
.admin-card {
margin: 20px;
padding: 20px;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
mat-form-field {
flex: 1;
}
}
.table-container {
margin-top: 20px;
}
.table-actions {
margin-bottom: 16px;
}
.acl-table {
width: 100%;
}
mat-form-field {
width: 100%;
}
.mat-column-actions {
width: 80px;
text-align: center;
}
.mat-column-accessLevel {
width: 150px;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DeploymentAdminComponent } from './deployment-admin.component';
describe('DeploymentAdminComponent', () => {
let component: DeploymentAdminComponent;
let fixture: ComponentFixture<DeploymentAdminComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DeploymentAdminComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DeploymentAdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,120 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatCardModule } from '@angular/material/card';
import { MatSelectModule } from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatTableModule } from '@angular/material/table';
import { MatIconModule } from '@angular/material/icon';
interface Beamline {
id: string;
name: string;
}
interface Deployment {
id: string;
name: string;
beamlineId: string;
}
interface ACLEntry {
userId: string;
username: string;
accessLevel: string;
lastModified: Date;
}
@Component({
selector: 'app-deployment-admin',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatSelectModule,
MatFormFieldModule,
MatButtonModule,
ReactiveFormsModule,
MatTableModule,
MatIconModule,
],
templateUrl: './deployment-admin.component.html',
styleUrl: './deployment-admin.component.scss',
})
export class DeploymentAdminComponent implements OnInit {
form: FormGroup;
beamlines: Beamline[] = [
{ id: 'x10sa', name: 'X10SA' },
{ id: 'x12sa', name: 'X12SA' },
];
deployments: Deployment[] = [];
aclEntries: ACLEntry[] = [];
displayedColumns: string[] = [
'username',
'accessLevel',
'lastModified',
'actions',
];
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
beamline: [''],
deployment: [''],
});
}
ngOnInit() {
// Subscribe to beamline selection changes
this.form.get('beamline')?.valueChanges.subscribe((beamlineId) => {
if (beamlineId) {
this.loadDeployments(beamlineId);
}
});
// Subscribe to deployment selection changes
this.form.get('deployment')?.valueChanges.subscribe((deploymentId) => {
if (deploymentId) {
this.loadACLEntries(deploymentId);
}
});
}
loadDeployments(beamlineId: string) {
// TODO: Replace with actual API call
this.deployments = [
{ id: 'dep1', name: 'Production', beamlineId },
{ id: 'dep2', name: 'Development', beamlineId },
];
}
loadACLEntries(deploymentId: string) {
// TODO: Replace with actual API call
this.aclEntries = [
{
userId: '1',
username: 'john.doe',
accessLevel: 'admin',
lastModified: new Date(),
},
{
userId: '2',
username: 'jane.smith',
accessLevel: 'user',
lastModified: new Date(),
},
];
}
addUser() {
// TODO: Implement user addition logic
}
removeUser(userId: string) {
// TODO: Implement user removal logic
}
updateAccessLevel(userId: string, newLevel: string) {
// TODO: Implement access level update logic
}
}

View File

@ -0,0 +1 @@
<gridstack [options]="gridOptions"></gridstack>

View File

@ -0,0 +1,50 @@
@import "gridstack/dist/gridstack.min.css";
@import "gridstack/dist/gridstack-extra.min.css"; // if you use 2-11 column
// .grid-stack {
// background: #fafad2;
// }
// .grid-stack-item-content {
// text-align: center;
// background-color: #18bc9c;
// }
:host {
display: block;
height: 100vh; /* Full-screen height */
width: 100vw; /* Full-screen width */
// overflow: hidden;
}
.grid-stack {
display: block;
// overflow: hidden;
height: 100%;
min-height: 100% !important;
}
.grid-stack-item-content {
display: flex;
justify-content: center;
align-items: center;
// background: #007bff;
// background-color: #18bc9c;
color: rgb(24, 7, 7);
// border: 1px solid #ddd;
}
$columns: 20;
@function fixed($float) {
@return round($float * 1000) / 1000; // total 2+3 digits being %
}
.gs-#{$columns} > .grid-stack-item {
width: fixed(100% / $columns);
@for $i from 1 through $columns - 1 {
&[gs-x="#{$i}"] {
left: fixed((100% / $columns) * $i);
}
&[gs-w="#{$i+1}"] {
width: fixed((100% / $columns) * ($i + 1));
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OverviewGridComponent } from './overview-grid.component';
describe('OverviewGridComponent', () => {
let component: OverviewGridComponent;
let fixture: ComponentFixture<OverviewGridComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [OverviewGridComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OverviewGridComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,59 @@
import { Component } from '@angular/core';
import {
GridstackComponent,
GridstackItemComponent,
NgGridStackOptions,
} from 'gridstack/dist/angular';
import { CommonModule } from '@angular/common';
import { GridStackOptions } from 'gridstack';
@Component({
selector: 'app-overview-grid',
imports: [GridstackComponent, GridstackItemComponent, CommonModule],
templateUrl: './overview-grid.component.html',
styleUrl: './overview-grid.component.scss',
})
export class OverviewGridComponent {
public gridOptions: NgGridStackOptions = {
margin: 1,
minRow: 8, // make space for empty message
// staticGrid: true,
cellHeight: 100,
float: true,
columnOpts: {
breakpointForWindow: true,
breakpoints: [
{ w: 800, c: 1 },
{ w: 1000, c: 10 },
],
},
// disableResize: true,
children: [
// or call load()/addWidget() with same data
{
x: 1,
y: 10,
minW: 1,
selector: 'app-device-box',
input: { device: 'samx', signal_name: 'samx' },
},
{
x: 2,
y: 5,
minW: 1,
selector: 'app-device-box',
input: { device: 'samy', signal_name: 'samy' },
},
{
x: 3,
y: 5,
minW: 1,
selector: 'app-device-box',
input: { device: 'samx', signal_name: 'samx' },
},
// {x:1, y:0, minW:2, selector:'app-a', input: { text: 'bar' }}, // custom input that works using BaseWidget.deserialize() Object.assign(this, w.input)
// {x:2, y:0, selector:'app-b'},
{ x: 3, y: 0, content: 'plain html' },
],
};
}