Skip to content

Commit

Permalink
Merge pull request #247 from cobbler/feature/cobbler-status
Browse files Browse the repository at this point in the history
Feature: Add UI for ` cobbler status`
  • Loading branch information
SchoolGuy authored Jul 13, 2024
2 parents 96c78c5 + 3289a62 commit 54312c6
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 9 deletions.
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

0 comments on commit 54312c6

Please sign in to comment.