Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add UI for cobbler status #247

Merged
merged 2 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions projects/cobbler-api/src/lib/cobbler-api.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {TestBed} from '@angular/core/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {Event, ExtendedVersion} from './custom-types/misc';
import {Event, ExtendedVersion, InstallationStatus} from './custom-types/misc';
import {COBBLER_URL} from './lib.config';
import {AngularXmlrpcService} from 'typescript-xmlrpc';

Expand Down Expand Up @@ -994,9 +994,27 @@ describe('CobblerApiService', () => {
expect(service).toBeFalsy();
});

xit('should execute the get_status action on the Cobbler Server', () => {
service.get_status('', '');
expect(service).toBeFalsy();
it('should execute the get_status action on the Cobbler Server', (done: DoneFn) => {
// eslint-disable-next-line max-len
const methodResponse = `<?xml version='1.0'?><methodResponse><params><param><value><struct><member><name>127.0.0.1</name><value><array><data><value><double>1720873663.7566895</double></value><value><double>1720873696.675196</double></value><value><string>system:testsys</string></value><value><int>1</int></value><value><int>2</int></value><value><string>finished</string></value></data></array></value></member></struct></value></param></params></methodResponse>`

const result: Array<InstallationStatus> = [
{
ip: "127.0.0.1",
mostRecentStart: 1720873663.7566895,
mostRecentStop: 1720873696.675196,
mostRecentTarget: 'system:testsys',
seenStart: 1,
seenStop: 2,
state: 'finished'
}
]
service.get_status('normal', 'alksjdbskjdbakljdbsaajkiuhgzulnbgtz').subscribe(value => {
expect(value).toEqual(result);
done();
});
const mockRequest = httpTestingController.expectOne('http://localhost/cobbler_api');
mockRequest.flush(methodResponse);
});

xit('should execute the check_access_no_fail action on the Cobbler Server', () => {
Expand Down
30 changes: 25 additions & 5 deletions projects/cobbler-api/src/lib/cobbler-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ import {
BackgroundImportOptions,
BackgroundPowerSystem,
BackgroundReplicateOptions,
BackgroundReposyncOptions, Event,
ExtendedVersion, PagesItemsResult, RegisterOptions,
BackgroundReposyncOptions,
Event,
ExtendedVersion,
InstallationStatus,
PagesItemsResult,
RegisterOptions,
SyncOptions,
SyncSystemsOptions,
Version
Expand Down Expand Up @@ -2675,17 +2679,33 @@ export class CobblerApiService {
);
}

get_status(mode: string, token: string): Observable<object> {
get_status(mode: string, token: string): Observable<Array<InstallationStatus>> {
return this.client
.methodCall('get_status', [mode, token])
.pipe(
map<MethodResponse | MethodFault, object>((data: MethodResponse | MethodFault) => {
map<MethodResponse | MethodFault, XmlRpcStruct>((data: MethodResponse | MethodFault) => {
if (AngularXmlrpcService.instanceOfMethodResponse(data)) {
return data.value as object;
return data.value as XmlRpcStruct;
} else if (AngularXmlrpcService.instanceOfMethodFault(data)) {
throw new Error('Getting the status failed with code "' + data.faultCode + '" and error message "'
+ data.faultString + '"');
}
}),
map<XmlRpcStruct, Array<InstallationStatus>>((data: XmlRpcStruct) => {
let result: Array<InstallationStatus> = [];
data.members.forEach( element => {
const membersArray = element.value as XmlRpcArray
result.push({
ip: element.name,
mostRecentStart: membersArray.data[0] as number,
mostRecentStop: membersArray.data[1] as number,
mostRecentTarget: membersArray.data[2] as string,
seenStart: membersArray.data[3] as number,
seenStop: membersArray.data[4] as number,
state: membersArray.data[5] as string,
})
})
return result;
})
);
}
Expand Down
10 changes: 10 additions & 0 deletions projects/cobbler-api/src/lib/custom-types/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,13 @@ export interface Event {
state: string
readByWho: Array<string>
}

export interface InstallationStatus {
ip: string;
mostRecentStart: number;
mostRecentStop: number;
mostRecentTarget: string;
seenStart: number;
seenStop: number;
state: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<h1 class="title">Installation Status</h1>

<mat-form-field>
<mat-label>Filter</mat-label>
<input matInput (keyup)="applyFilter($event)" placeholder="Ex. 10.0.0.1" #input>
</mat-form-field>

<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>

<!-- IP Column -->
<ng-container matColumnDef="ip">
<th mat-header-cell *matHeaderCellDef> IP</th>
<td mat-cell *matCellDef="let element"> {{ element.ip }}</td>
</ng-container>

<!-- Object Type Column -->
<ng-container matColumnDef="objType">
<th mat-header-cell *matHeaderCellDef> Object Type</th>
<td mat-cell *matCellDef="let element"> {{ element.mostRecentTarget.split(":")[0] }}</td>
</ng-container>

<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name</th>
<td mat-cell *matCellDef="let element"> {{ element.mostRecentTarget.split(":")[1] }}</td>
</ng-container>

<!-- Most Recent Start Column -->
<ng-container matColumnDef="mostRecentStart">
<th mat-header-cell *matHeaderCellDef> Most Recent Start</th>
<td mat-cell *matCellDef="let element"> {{ element.mostRecentStart * 1000 | date:'short' }}</td>
</ng-container>

<!-- Most Recent Stop Column -->
<ng-container matColumnDef="mostRecentStop">
<th mat-header-cell *matHeaderCellDef> Most Recent Stop</th>
<td mat-cell *matCellDef="let element"> {{ element.mostRecentStop * 1000 | date:'short' }}</td>
</ng-container>

<!-- Seen Start Column -->
<ng-container matColumnDef="seenStart">
<th mat-header-cell *matHeaderCellDef> Seen Start Counter</th>
<td mat-cell *matCellDef="let element"> {{ element.seenStop }}</td>
</ng-container>

<!-- Seen Stop Column -->
<ng-container matColumnDef="seenStop">
<th mat-header-cell *matHeaderCellDef> Seen Stop Counter</th>
<td mat-cell *matCellDef="let element"> {{ element.seenStop }}</td>
</ng-container>

<!-- State Column -->
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> State</th>
<td mat-cell *matCellDef="let element"> {{ element.state }}</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>

<!-- Row shown when there is no matching data. -->
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4">No data matching the filter "{{ input.value }}"</td>
</tr>
</table>

<!-- Paginator -->
<mat-paginator [pageSizeOptions]="[5, 10, 25, 100]" aria-label="Select number of installations"></mat-paginator>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {provideHttpClient} from '@angular/common/http';
import {provideHttpClientTesting} from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {MatPaginatorModule} from '@angular/material/paginator';
import {MatTableModule} from '@angular/material/table';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {COBBLER_URL} from 'cobbler-api';

import { StatusComponent } from './status.component';

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

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
StatusComponent,
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatPaginatorModule,
NoopAnimationsModule,
],
providers: [
{
provide: COBBLER_URL,
useValue: new URL('http://localhost/cobbler_api')
},
provideHttpClient(),
provideHttpClientTesting(),
]
})
.compileComponents();

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

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {DatePipe} from '@angular/common';
import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatInputModule} from '@angular/material/input';
import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator';
import {MatSort} from '@angular/material/sort';
import {MatTableDataSource, MatTableModule} from '@angular/material/table';
import {CobblerApiService, InstallationStatus} from 'cobbler-api';
import {UserService} from '../../services/user.service';


@Component({
selector: 'cobbler-status',
standalone: true,
imports: [
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatPaginatorModule,
MatSort,
DatePipe,
],
templateUrl: './status.component.html',
styleUrl: './status.component.scss'
})
export class StatusComponent implements OnInit, AfterViewInit {
displayedColumns: string[] = [
'ip',
'objType',
'name',
'mostRecentStart',
'mostRecentStop',
'seenStart',
'seenStop',
'state'
];
dataSource: MatTableDataSource<InstallationStatus> = new MatTableDataSource([]);

@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;

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

ngOnInit(): void {
this.cobblerApiService.get_status("normal", this.userService.token).subscribe((value) => {
console.log(value)
this.dataSource.data = value
})
}

ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}

applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();

if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
}
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 @@ -4,6 +4,7 @@ import { BuildISOComponent } from './actions/build-iso/build-iso.component';
import { CheckSysComponent } from './actions/check-sys/check-sys.component';
import { ImportDVDComponent } from './actions/import-dvd/import-dvd.component';
import { RepoSyncComponent } from './actions/repo-sync/repo-sync.component';
import {StatusComponent} from './actions/status/status.component';
import { SyncComponent } from './actions/sync/sync.component';
import { AppEventsComponent } from './app-events/app-events.component';
import { AppManageComponent } from './appManage';
Expand Down Expand Up @@ -46,6 +47,7 @@ export const routes: Routes = [
{path: 'reposync', component: RepoSyncComponent, canActivate: [AuthGuardService]},
{path: 'buildiso', component: BuildISOComponent, canActivate: [AuthGuardService]},
{path: 'check', component: CheckSysComponent, canActivate: [AuthGuardService]},
{path: 'status', component: StatusComponent, canActivate: [AuthGuardService]},
{path: 'events', component: AppEventsComponent, canActivate: [AuthGuardService]},
{path: '404', component: NotFoundComponent},
{path: '**', redirectTo: '/404'},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ <h2 matSubheader>Cobbler</h2>
<b class="symbol">&#8646;</b>
Check</a
>
<a mat-list-item class="nav-link" [routerLink]="['/status']">
<b class="symbol">&#8646;</b>
Status</a
>
<a mat-list-item class="nav-link" [routerLink]="['/events']">
<b class="symbol">&#8646;</b>
Events</a
Expand Down
Loading