mirror of
https://github.com/bec-project/bec_atlas.git
synced 2025-07-14 07:01:48 +02:00
fix: added missing overview grid and admin components
This commit is contained in:
@ -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>
|
@ -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;
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
<gridstack [options]="gridOptions"></gridstack>
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
@ -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' },
|
||||
],
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user