Skip to content

Commit

Permalink
Cobbler-Frontend: Add UI for signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
SchoolGuy committed Jul 18, 2024
1 parent 7548131 commit 88aae78
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 0 deletions.
2 changes: 2 additions & 0 deletions projects/cobbler-frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { NotFoundComponent } from './not-found/not-found.component';
import { AuthGuardService } from './services/auth-guard.service';
import { SettingsViewComponent } from './settings/view/settings-view.component';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
import {SignaturesComponent} from "./signatures/signatures.component";


export const routes: Routes = [
Expand Down Expand Up @@ -51,6 +52,7 @@ export const routes: Routes = [
{path: 'status', component: StatusComponent, canActivate: [AuthGuardService]},
{path: 'hardlink', component: HardlinkComponent, canActivate: [AuthGuardService]},
{path: 'events', component: AppEventsComponent, canActivate: [AuthGuardService]},
{path: 'signatures', component: SignaturesComponent, canActivate: [AuthGuardService]},
{path: '404', component: NotFoundComponent},
{path: '**', redirectTo: '/404'},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ <h2 matSubheader>Cobbler</h2>
<b class="symbol">&#8646;</b>
Events</a
>
<a mat-list-item class="nav-link" [routerLink]="['/signatures']">
<b class="symbol">&#8646;</b>
Signatures</a
>
<a
mat-list-item
class="nav-link"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<div class="title-table">
<div class="title-row">
<h1 class="title title-cell-text">Signatures</h1>
<span class="title-cell-button">
<button mat-icon-button (click)="this.updateSignatures()"><mat-icon>refresh</mat-icon></button>
</span>
</div>
</div>

<mat-spinner *ngIf="isLoading" style="margin:0 auto;"></mat-spinner>
<ng-container *ngIf="!isLoading">
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<!-- This is the tree node template for leaf nodes -->
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<!-- use a disabled button to provide padding for tree leaf -->
<button mat-icon-button disabled></button>
{{ node.data }}
</mat-tree-node>
<!-- This is the tree node template for expandable nodes -->
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'Toggle ' + node.data">
<mat-icon class="mat-icon-rtl-mirror">
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
{{ node.data }}
</mat-tree-node>
<!-- This is the tree node template for expandable nodes with an OS version as data -->
<mat-tree-node *matTreeNodeDef="let node;when: hasOsVersion" matTreeNodePadding>
<button mat-icon-button disabled></button>
<table mat-table [dataSource]="node.data" class="mat-elevation-z8">
@for (column of columns; track column) {
<ng-container [matColumnDef]="column.columnDef">
<th mat-header-cell *matHeaderCellDef>
{{ column.header }}
</th>
<td mat-cell *matCellDef="let row">
{{ column.cell(row) }}
</td>
</ng-container>
}
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</mat-tree-node>
</mat-tree>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.title-table {
display: table;
width: 100%;
}

.title-row {
display: table-cell;
width: 100%;
}

.title-cell-text {
display: table-cell;
width: 100%;
vertical-align: middle;
}

.title-cell-button {
display: table-cell;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {provideHttpClient} from '@angular/common/http';
import {provideHttpClientTesting} from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import {MatDividerModule} from '@angular/material/divider';
import {MatIconModule} from '@angular/material/icon';
import {MatTableModule} from '@angular/material/table';
import {MatTreeModule} from '@angular/material/tree';
import {COBBLER_URL} from 'cobbler-api';

import { SignaturesComponent } from './signatures.component';

describe('SignaturesComponent', () => {
let component: SignaturesComponent;
let fixture: ComponentFixture<SignaturesComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
SignaturesComponent,
MatTreeModule,
MatIconModule,
MatTableModule,
MatDividerModule,
],
providers: [
provideHttpClient(),
provideHttpClientTesting(),
{
provide: COBBLER_URL,
useValue: new URL('http://localhost/cobbler_api')
},
]
})
.compileComponents();

fixture = TestBed.createComponent(SignaturesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
186 changes: 186 additions & 0 deletions projects/cobbler-frontend/src/app/signatures/signatures.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import {AsyncPipe, NgForOf, NgIf} from '@angular/common';
import {Component, OnInit} from '@angular/core';
import {MatDivider} from '@angular/material/divider';
import {MatList, MatListItem} from '@angular/material/list';
import {MatProgressSpinner} from '@angular/material/progress-spinner';
import {MatSnackBar} from '@angular/material/snack-bar';
import {
MatCell, MatCellDef,
MatColumnDef,
MatHeaderCell,
MatHeaderCellDef,
MatHeaderRow, MatHeaderRowDef,
MatRow, MatRowDef,
MatTable
} from '@angular/material/table';
import {filter, repeat, take} from 'rxjs/operators';
import {UserService} from '../services/user.service';
import {CobblerApiService} from 'cobbler-api';
import {
MatTree,
MatTreeFlatDataSource,
MatTreeFlattener,
MatTreeNode, MatTreeNodeDef,
MatTreeNodePadding,
MatTreeNodeToggle
} from '@angular/material/tree';
import {FlatTreeControl} from '@angular/cdk/tree';
import {MatIcon} from '@angular/material/icon';
import {MatIconButton} from '@angular/material/button';

interface TableRow {
key: string;
value: any;
}

/**
* Food data with nested structure.
* Each node has a name and an optional list of children.
*/
interface OsNode {
data: string | Array<TableRow>;
children?: OsNode[];
}

/** Flat node with expandable and level information */
interface OsBreedFlatNode {
expandable: boolean;
data: string | Array<TableRow>;
level: number;
}

@Component({
selector: 'cobbler-signatures',
standalone: true,
imports: [
MatTree,
MatTreeNode,
MatIcon,
MatIconButton,
MatTreeNodeToggle,
MatTreeNodePadding,
MatTreeNodeDef,
MatTable,
MatHeaderCell,
MatCell,
MatHeaderRow,
MatRow,
MatColumnDef,
MatHeaderCellDef,
MatCellDef,
MatHeaderRowDef,
MatRowDef,
MatDivider,
AsyncPipe,
MatList,
MatListItem,
MatProgressSpinner,
NgForOf,
NgIf
],
templateUrl: './signatures.component.html',
styleUrl: './signatures.component.scss'
})
export class SignaturesComponent implements OnInit {
// Table
columns = [
{
columnDef: 'key',
header: 'Attribute',
cell: (element: TableRow) => `${element.key}`,
},
{
columnDef: 'value',
header: 'Value',
cell: (element: TableRow) => `${element.value}`,
},
];

displayedColumns = this.columns.map(c => c.columnDef);

// Tree
private _transformer = (node: OsNode, level: number) => {
return {
expandable: !!node.children && node.children.length > 0,
data: node.data,
level: level,
};
};

treeControl = new FlatTreeControl<OsBreedFlatNode>(
node => node.level,
node => node.expandable,
);

treeFlattener = new MatTreeFlattener(
this._transformer,
node => node.level,
node => node.expandable,
node => node.children,
);
dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);

// Spinner
public isLoading = true;

constructor(
public userService: UserService,
private cobblerApiService: CobblerApiService,
private _snackBar: MatSnackBar,
) {
}

ngOnInit(): void {
this.generateSignatureUiData();
}

hasChild = (_: number, node: OsBreedFlatNode) => node.expandable;

hasOsVersion = (_: number, node: OsBreedFlatNode) => typeof node.data !== 'string';

generateSignatureUiData(): void {
this.cobblerApiService.get_signatures(this.userService.token).subscribe(value => {
const newData: Array<OsNode> = [];
for (const k in value.breeds) {
const children: Array<OsNode> = [];
for (const j in value.breeds[k]) {
const osVersionData: Array<TableRow> = [];
for (const i in value.breeds[k][j]) {
osVersionData.push({key: i, value: value.breeds[k][j][i]});
}
children.push({data: j, children: [{data: osVersionData, children: []}]});
}
newData.push({data: k, children: children});
}
this.dataSource.data = newData;
this.isLoading = false
}, error => {
// HTML encode the error message since it originates from XML
this._snackBar.open(this.toHTML(error.message), 'Close');
});
}

updateSignatures(): void {
this.isLoading = true
this.cobblerApiService.background_signature_update(this.userService.token).subscribe(
value => {
this.cobblerApiService.get_task_status(value).pipe(
repeat(),
filter(data => data.state === "failed" || data.state === "complete"),
take(1)
).subscribe(value1 => {
this.isLoading = false
this.generateSignatureUiData()
})
},
error => {
// HTML encode the error message since it originates from XML
this._snackBar.open(this.toHTML(error.message), 'Close');
});
}

toHTML(input: string): any {
// FIXME: Deduplicate method
return new DOMParser().parseFromString(input, 'text/html').documentElement.textContent;
}
}

0 comments on commit 88aae78

Please sign in to comment.