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

Add event log viewer #235

Merged
merged 2 commits into from
Jul 8, 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
102 changes: 92 additions & 10 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 {ExtendedVersion} from './custom-types/misc';
import {Event, ExtendedVersion} from './custom-types/misc';
import {COBBLER_URL} from './lib.config';
import {AngularXmlrpcService} from 'typescript-xmlrpc';

Expand Down Expand Up @@ -132,19 +132,101 @@ describe('CobblerApiService', () => {
expect(service).toBeFalsy();
});

xit('should execute the get_events action on the Cobbler Server', () => {
service.get_events('');
expect(service).toBeFalsy();
it('should execute the get_events action on the Cobbler Server', () => {
// eslint-disable-next-line max-len
const methodResponse = `<?xml version='1.0'?><methodResponse><params><param><value><struct><member><name>2023-01-24_075223_Create bootable bootloader images_77c9dbafc9234f018d67ec3295fcc22b</name><value><array><data><value><double>1674546743.8418643</double></value><value><string>Create bootable bootloader images</string></value><value><string>complete</string></value><value><array><data><value><string>1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ==</string></value></data></array></value></data></array></value></member><member><name>2023-01-24_075801_Replicate_ea7a003a81264039b4277ac55664661a</name><value><array><data><value><double>1674547081.1178503</double></value><value><string>Replicate</string></value><value><string>failed</string></value><value><array><data><value><string>1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ==</string></value></data></array></value></data></array></value></member><member><name>2023-01-24_083001_Build Iso_20fa7d4256fc4f61a2b9c2237c80fb41</name><value><array><data><value><double>1674549001.176315</double></value><value><string>Build Iso</string></value><value><string>failed</string></value><value><array><data><value><string>1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ==</string></value></data></array></value></data></array></value></member><member><name>2023-01-24_083137_(CLI) ACL Configuration_334327920d2946fda3ac95dbf457e76d</name><value><array><data><value><double>1674549097.240632</double></value><value><string>(CLI) ACL Configuration</string></value><value><string>failed</string></value><value><array><data><value><string>1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ==</string></value></data></array></value></data></array></value></member></struct></value></param></params></methodResponse>`
const result : Array<Event> = [
{
id: "2023-01-24_075223_Create bootable bootloader images_77c9dbafc9234f018d67ec3295fcc22b",
statetime: 1674546743.8418643,
name: "Create bootable bootloader images",
state: "complete",
readByWho: ["1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ=="]
},
{
id: "2023-01-24_075801_Replicate_ea7a003a81264039b4277ac55664661a",
statetime: 1674547081.1178503,
name: "Replicate",
state: "failed",
readByWho: ["1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ=="]
},
{
id: "2023-01-24_083001_Build Iso_20fa7d4256fc4f61a2b9c2237c80fb41",
statetime: 1674549001.176315,
name: "Build Iso",
state: "failed",
readByWho: ["1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ=="]
},
{
id: "2023-01-24_083137_(CLI) ACL Configuration_334327920d2946fda3ac95dbf457e76d",
statetime: 1674549097.240632,
name: "(CLI) ACL Configuration",
state: "failed",
readByWho: ["1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ=="]
}
];
service.get_events('').subscribe(value => {
expect(value).toEqual(result)
});
const mockRequest = httpTestingController.expectOne('http://localhost/cobbler_api');
mockRequest.flush(methodResponse);
});

xit('should execute the get_event_log action on the Cobbler Server', () => {
service.get_event_log('');
expect(service).toBeFalsy();
it('should execute the get_event_log action on the Cobbler Server', () => {
/* eslint-disable max-len */
const methodResponse = `<?xml version='1.0'?><methodResponse><params><param><value><string>[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,744 - INFO | start_task(sync); event_id(2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29)
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,744 - INFO | syncing all
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,745 - INFO | copying: /var/lib/cobbler/misc/anamon.init -&gt; /srv/www/cobbler/misc
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,745 - INFO | copying: /var/lib/cobbler/misc/anamon -&gt; /srv/www/cobbler/misc
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,745 - INFO | running pre-sync triggers
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,746 - INFO | cleaning trees
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,746 - INFO | removing: /srv/tftpboot/pxelinux.cfg
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,746 - INFO | removing: /srv/tftpboot/grub
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,746 - INFO | removing: /srv/tftpboot/images
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,746 - INFO | removing: /srv/tftpboot/ipxe
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,746 - INFO | removing: /srv/tftpboot/esxi
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,747 - INFO | removing: /srv/www/cobbler/rendered
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,747 - INFO | copying bootloaders
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,747 - INFO | running: ['rsync', '-rpt', '--copy-links', '--exclude=.cobbler_postun_cleanup', '/var/lib/cobbler/loaders/', '/srv/tftpboot']
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,936 - INFO | received on stdout:
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,936 - INFO | running: ['rsync', '-rpt', '--copy-links', '--exclude=README.grubconfig', '/var/lib/cobbler/grub_config/', '/srv/tftpboot']
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,942 - INFO | received on stdout:
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,942 - INFO | copying distros to tftpboot
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,943 - INFO | copying images
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,943 - INFO | generating PXE configuration files
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,943 - INFO | generating PXE menu structure
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,945 - INFO | rendering DHCP files
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,945 - INFO | generating /etc/dhcpd.conf
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,962 - INFO | cleaning link caches
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,962 - INFO | running post-sync triggers
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:24,962 - INFO | running: ['dhcpd', '-t', '-q']
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:25,002 - INFO | received on stdout:
[2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29] 2022-09-30 14:51:26,044 - INFO | ### TASK COMPLETE ###</string></value></param></params></methodResponse>`
/* eslint-enable max-len */
service.get_event_log('2022-09-30_145124_Sync_2cabdc4eddfa4731b45f145d7b625e29').subscribe(value => [
expect(value.split(/\r?\n/).length).toEqual(28)
]);
const mockRequest = httpTestingController.expectOne('http://localhost/cobbler_api');
mockRequest.flush(methodResponse);
});

xit('should execute the get_task_status action on the Cobbler Server', () => {
service.get_task_status('');
expect(service).toBeFalsy();
it('should execute the get_task_status action on the Cobbler Server', () => {
// eslint-disable-next-line max-len
const methodResponse = `<?xml version='1.0'?><methodResponse><params><param><value><array><data><value><double>1664568243.5196018</double></value><value><string>Updating Signatures</string></value><value><string>complete</string></value><value><array><data></data></array></value></data></array></value></param></params></methodResponse>`
const result = {
id: "2022-09-30_200403_Updating Signatures_8f2b3c1626fb4b158636059b31242ee6",
statetime: 1664568243.5196018,
name: "Updating Signatures",
state: "complete",
readByWho: []
}
service.get_task_status(
'2022-09-30_200403_Updating Signatures_8f2b3c1626fb4b158636059b31242ee6'
).subscribe(value => {
expect(value).toEqual(result)
});
const mockRequest = httpTestingController.expectOne('http://localhost/cobbler_api');
mockRequest.flush(methodResponse);
});

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

get_events(forUser: string): Observable<object> {
get_events(forUser: string): Observable<Array<Event>> {
return this.client
.methodCall('background_signature_update', [forUser])
.methodCall('get_events', [forUser])
.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 events failed with code "' + data.faultCode + '" and error message "'
+ data.faultString + '"');
}
}),
map<XmlRpcStruct, Array<Event>>((data: XmlRpcStruct) => {
let result: Array<Event> = [];
data.members.forEach( element => {
const membersArray = element.value as XmlRpcArray
const usersArray = membersArray.data[3] as XmlRpcArray
result.push({
id: element.name,
statetime: membersArray.data[0] as number,
name: membersArray.data[1] as string,
state: membersArray.data[2] as string,
readByWho: usersArray.data as string[]
})
})
return result;
})
);
}
Expand All @@ -346,17 +361,27 @@ export class CobblerApiService {
);
}

get_task_status(eventId: string): Observable<object> {
get_task_status(eventId: string): Observable<Event> {
return this.client
.methodCall('get_task_status', [eventId])
.pipe(
map<MethodResponse | MethodFault, object>((data: MethodResponse | MethodFault) => {
map<MethodResponse | MethodFault, XmlRpcArray>((data: MethodResponse | MethodFault) => {
if (AngularXmlrpcService.instanceOfMethodResponse(data)) {
return data.value as object;
return data.value as XmlRpcArray;
} else if (AngularXmlrpcService.instanceOfMethodFault(data)) {
throw new Error('Getting the status of the requested task failed with code "' + data.faultCode
+ '" and error message "' + data.faultString + '"');
}
}),
map<XmlRpcArray, Event>((data: XmlRpcArray) => {
const readByWho = data.data[3] as XmlRpcArray
return {
id: eventId,
statetime: data.data[0] as number,
name: data.data[1] as string,
state: data.data[2] as string,
readByWho: readByWho.data as string[],
}
})
);
}
Expand Down
8 changes: 8 additions & 0 deletions projects/cobbler-api/src/lib/custom-types/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,11 @@ export interface PageInfo {
items_per_page: number;
items_per_page_list: [10, 20, 50, 100, 200, 500];
}

export interface Event {
id: string
statetime: number
name: string
state: string
readByWho: Array<string>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
table {
width: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,47 @@

<h1 class="title">EVENTS</h1>

<mat-list>
<mat-list-item>data 01</mat-list-item>
<mat-list-item>data 02</mat-list-item>
<mat-list-item>data 03</mat-list-item>
</mat-list>
<table mat-table [dataSource]="cobblerEvents">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{ element.name }} </td>
</ng-container>

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

<ng-container matColumnDef="statetime">
<th mat-header-cell *matHeaderCellDef> Date </th>
<td mat-cell *matCellDef="let element"> {{ element.statetime * 1000 | date:'long' }} </td>
</ng-container>

<ng-container matColumnDef="readByWho">
<th mat-header-cell *matHeaderCellDef> Read by </th>
<td mat-cell *matCellDef="let element">
<ng-container *ngIf="element.readByWho.length > 0">
- <ng-container *ngFor="let item of element.readByWho"> {{item}} - </ng-container> -
</ng-container>
<ng-container *ngIf="element.readByWho.length === 0"> - </ng-container>
</td>
</ng-container>

<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element">
<button mat-icon-button [matMenuTriggerFor]="menu"><mat-icon>more_vert</mat-icon></button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="showLogs(element.id, element.name)">
<mat-icon>description</mat-icon>
<span>Show Logs</span>
</button>
</mat-menu>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,52 @@ import { MatListModule } from '@angular/material/list';
import {provideRouter} from '@angular/router';

import { AppEventsComponent } from './app-events.component';
import {MatDialogModule} from "@angular/material/dialog";
import {HttpClientTestingModule, HttpTestingController} from "@angular/common/http/testing";
import {COBBLER_URL} from 'cobbler-api';


describe('AppEventsComponent', () => {
let component: AppEventsComponent;
let fixture: ComponentFixture<AppEventsComponent>;
let httpTestingController: HttpTestingController;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppEventsComponent, MatListModule],
imports: [
AppEventsComponent,
MatListModule,
HttpClientTestingModule,
MatDialogModule,
],
providers: [
provideRouter([]),
{
provide: COBBLER_URL,
useValue: new URL('http://localhost/cobbler_api')
},
]
}).compileComponents();
});

beforeEach(() => {
httpTestingController = TestBed.inject(HttpTestingController);
fixture = TestBed.createComponent(AppEventsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

afterEach(() => {
// After every test, assert that there are no more pending requests.
httpTestingController.verify();
});

it('should create', () => {
// FIXME: Deduplicate stub with API tests
// eslint-disable-next-line max-len
const methodResponse = `<?xml version='1.0'?><methodResponse><params><param><value><struct><member><name>2023-01-24_075223_Create bootable bootloader images_77c9dbafc9234f018d67ec3295fcc22b</name><value><array><data><value><double>1674546743.8418643</double></value><value><string>Create bootable bootloader images</string></value><value><string>complete</string></value><value><array><data><value><string>1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ==</string></value></data></array></value></data></array></value></member><member><name>2023-01-24_075801_Replicate_ea7a003a81264039b4277ac55664661a</name><value><array><data><value><double>1674547081.1178503</double></value><value><string>Replicate</string></value><value><string>failed</string></value><value><array><data><value><string>1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ==</string></value></data></array></value></data></array></value></member><member><name>2023-01-24_083001_Build Iso_20fa7d4256fc4f61a2b9c2237c80fb41</name><value><array><data><value><double>1674549001.176315</double></value><value><string>Build Iso</string></value><value><string>failed</string></value><value><array><data><value><string>1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ==</string></value></data></array></value></data></array></value></member><member><name>2023-01-24_083137_(CLI) ACL Configuration_334327920d2946fda3ac95dbf457e76d</name><value><array><data><value><double>1674549097.240632</double></value><value><string>(CLI) ACL Configuration</string></value><value><string>failed</string></value><value><array><data><value><string>1YMw1KxyPQtPM8AVB5ejKFJryEHCtCwYQQ==</string></value></data></array></value></data></array></value></member></struct></value></param></params></methodResponse>`
const mockRequest = httpTestingController.expectOne('http://localhost/cobbler_api');
mockRequest.flush(methodResponse);
expect(component).toBeTruthy();
});
});
Loading
Loading