Skip to content

Commit

Permalink
NAS-125480: Add links to audit page (#9267)
Browse files Browse the repository at this point in the history
  • Loading branch information
undsoft authored Dec 1, 2023
1 parent debddef commit 74ce255
Show file tree
Hide file tree
Showing 101 changed files with 312 additions and 103 deletions.
2 changes: 1 addition & 1 deletion src/app/enums/audit.enum.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export enum AuditService {
Smb = 'smb',
Smb = 'SMB',
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@
<ix-icon name="delete"></ix-icon>
<span>{{ 'Delete' | translate }}</span>
</button>

<button
mat-button
[ixTest]="['logs', user.username]"
(click)="viewLogs(user)"
>
<ix-icon name="receipt_long"></ix-icon>
<span>{{ 'Audit Logs' | translate }}</span>
</button>
</ix-table-expandable-row>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { Spectator } from '@ngneat/spectator';
import { createComponentFactory, mockProvider } from '@ngneat/spectator/jest';
import { provideMockStore } from '@ngrx/store/testing';
Expand Down Expand Up @@ -111,4 +112,16 @@ describe('UserDetailsRowComponent', () => {
data: dummyUser,
});
});

it('navigates to audit logs page when Audit Logs button is pressed', async () => {
const router = spectator.inject(Router);
jest.spyOn(router, 'navigateByUrl').mockImplementation(() => Promise.resolve(true));

const auditButton = await loader.getHarness(MatButtonHarness.with({ text: /Audit Logs/ }));
await auditButton.click();

expect(router.navigateByUrl).toHaveBeenCalledWith(
'/system/audit/{"searchQuery":{"isBasicQuery":false,"filters":[["username","=","test-user"]]}}',
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Component, ChangeDetectionStrategy, Input, EventEmitter, Output,
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateService } from '@ngx-translate/core';
import { YesNoPipe } from 'app/core/pipes/yes-no.pipe';
Expand All @@ -12,6 +13,7 @@ import {
} from 'app/pages/account/users/user-details-row/delete-user-dialog/delete-user-dialog.component';
import { UserFormComponent } from 'app/pages/account/users/user-form/user-form.component';
import { IxSlideInService } from 'app/services/ix-slide-in.service';
import { UrlOptionsService } from 'app/services/url-options.service';

@UntilDestroy()
@Component({
Expand All @@ -28,6 +30,8 @@ export class UserDetailsRowComponent {
private slideInService: IxSlideInService,
private matDialog: MatDialog,
private yesNoPipe: YesNoPipe,
private urlOptions: UrlOptionsService,
private router: Router,
) {}

getDetails(user: User): Option[] {
Expand Down Expand Up @@ -90,6 +94,16 @@ export class UserDetailsRowComponent {
});
}

viewLogs(user: User): void {
const url = this.urlOptions.buildUrl('/system/audit', {
searchQuery: {
isBasicQuery: false,
filters: [['username', '=', user.username]],
},
});
this.router.navigateByUrl(url);
}

private getSshStatus(user: User): string {
const keySet = this.translate.instant('Key set');
const passwordLoginEnabled = this.translate.instant('Password login enabled');
Expand Down
4 changes: 3 additions & 1 deletion src/app/pages/audit/components/audit/audit.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ export class AuditComponent implements OnInit, AfterViewInit, OnDestroy {
}),
textColumn({
title: this.translate.instant('Event'),
getValue: (row) => this.translate.instant(auditEventLabels.get(row.event)),
getValue: (row) => (auditEventLabels.has(row.event)
? this.translate.instant(auditEventLabels.get(row.event))
: ''),
}),
textColumn({
title: this.translate.instant('Event Data'),
Expand Down
20 changes: 15 additions & 5 deletions src/app/pages/services/services.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,33 @@
<ng-container matColumnDef="actions">
<th *matHeaderCellDef mat-header-cell></th>
<td *matCellDef="let service; dataSource: dataSource" mat-cell class="actions">
<button
<a
*ngIf="hasLogs(service.service)"
mat-icon-button
type="button"
[ixTest]="[service.service, 'logs']"
[attr.aria-label]="'Audit Logs' | translate"
[matTooltip]="'Audit Logs' | translate"
[attr.name]="'logs-' + service.name"
[routerLink]="auditLogsUrl()"
>
<ix-icon name="receipt_long"></ix-icon>
</a>
<a
*ngIf="hasSessions(service.service)"
mat-icon-button
type="button"
matTooltipPosition="left"
[ixTest]="[service.service, 'sessions']"
[attr.aria-label]="'{name} Sessions' | translate: { name: serviceNames.get(service.service) }"
[matTooltip]="'{name} Sessions' | translate: { name: serviceNames.get(service.service) }"
[attr.name]="'sessions-' + service.name"
(click)="viewSessions(service.service)"
[routerLink]="sessionsUrl(service.service)"
>
<ix-icon name="list"></ix-icon>
</button>
</a>
<button
mat-icon-button
type="button"
matTooltipPosition="left"
[ixTest]="[service.service, 'edit']"
[attr.aria-label]="'Edit' | translate"
[attr.name]="'edit-' + service.name"
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/services/services.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@

:host ::ng-deep {
.ix-table.cdk-table .cdk-column-actions {
width: 50px;
width: auto;
}
}
169 changes: 84 additions & 85 deletions src/app/pages/services/services.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { MatButtonHarness } from '@angular/material/button/testing';
import { MatCheckboxHarness } from '@angular/material/checkbox/testing';
import { MatSlideToggleHarness } from '@angular/material/slide-toggle/testing';
import { Router } from '@angular/router';
import { SpectatorRouting } from '@ngneat/spectator';
import {
createRoutingFactory, mockProvider,
createComponentFactory, mockProvider, Spectator,
} from '@ngneat/spectator/jest';
import { provideMockStore } from '@ngrx/store/testing';
import { CoreComponents } from 'app/core/core-components.module';
Expand Down Expand Up @@ -47,11 +46,12 @@ const fakeDataSource: Service[] = [...serviceNames.entries()]
});

describe('ServicesComponent', () => {
let spectator: SpectatorRouting<ServicesComponent>;
let spectator: Spectator<ServicesComponent>;
let loader: HarnessLoader;
let ws: WebSocketService;
let table: IxTableHarness;

const createComponent = createRoutingFactory({
const createComponent = createComponentFactory({
component: ServicesComponent,
imports: [
CoreComponents,
Expand All @@ -61,7 +61,7 @@ describe('ServicesComponent', () => {
],
providers: [
mockWebsocket([
mockCall('service.update'),
mockCall('service.update', 1),
mockCall('service.start'),
mockCall('service.stop'),
]),
Expand All @@ -78,15 +78,14 @@ describe('ServicesComponent', () => {
],
});

beforeEach(() => {
beforeEach(async () => {
spectator = createComponent();
loader = TestbedHarnessEnvironment.loader(spectator.fixture);
ws = spectator.inject(WebSocketService);
spectator.fixture.detectChanges();
table = await loader.getHarness(IxTableHarness);
});

it('should show table rows', async () => {
const table = await loader.getHarness(IxTableHarness);
const cells = await table.getCells(true);
const expectedData = [...serviceNames.keys()]
.filter((service) => !hiddenServices.includes(service))
Expand All @@ -100,86 +99,80 @@ describe('ServicesComponent', () => {
expect(cells).toEqual(expectedRows);
});

it('should redirect to configure iSCSI service page when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-iSCSI"]' }));
await editButton.click();

expect(spectator.inject(Router).navigate).toHaveBeenCalledWith(['/sharing', 'iscsi']);
});

it('should open FTP configuration when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-FTP"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceFtpComponent, { wide: true });
});

it('should open NFS configuration when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-NFS"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceNfsComponent, { wide: true });
});

it('should open SNMP configuration when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-SNMP"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceSnmpComponent, { wide: true });
});

it('should open UPS configuration when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-UPS"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceUpsComponent, { wide: true });
});

it('should open SSH configuration when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-SSH"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceSshComponent);
});

it('should open SMB configuration when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-SMB"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceSmbComponent);
});

it('should navigate to view Sessions for SMB when Sessions button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="sessions-SMB"]' }));
await editButton.click();
expect(spectator.inject(Router).navigate).toHaveBeenCalledWith(['/sharing', 'smb', 'status', 'sessions']);
});

it('should navigate to view Sessions for NFS when Sessions button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="sessions-NFS"]' }));
await editButton.click();
expect(spectator.inject(Router).navigate).toHaveBeenCalledWith(['/sharing', 'nfs', 'sessions']);
});

it('should open S.M.A.R.T. configuration when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-S.M.A.R.T."]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceSmartComponent);
});

it('should open LLDP configuration when edit button is pressed', async () => {
const table = await loader.getHarness(IxTableHarness);
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-LLDP"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceLldpComponent);
describe('edit', () => {
it('should redirect to configure iSCSI service page when edit button is pressed', async () => {
const router = spectator.inject(Router);
jest.spyOn(router, 'navigate').mockResolvedValue(true);

// TODO: remove name attributes and rely on harnesses instead
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-iSCSI"]' }));
await editButton.click();

expect(router.navigate).toHaveBeenCalledWith(['/sharing', 'iscsi']);
});

it('should open FTP configuration when edit button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-FTP"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceFtpComponent, { wide: true });
});

it('should open NFS configuration when edit button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-NFS"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceNfsComponent, { wide: true });
});

it('should open SNMP configuration when edit button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-SNMP"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceSnmpComponent, { wide: true });
});

it('should open UPS configuration when edit button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-UPS"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceUpsComponent, { wide: true });
});

it('should open SSH configuration when edit button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-SSH"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceSshComponent);
});

it('should open SMB configuration when edit button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-SMB"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceSmbComponent);
});

it('should open S.M.A.R.T. configuration when edit button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-S.M.A.R.T."]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceSmartComponent);
});

it('should open LLDP configuration when edit button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="edit-LLDP"]' }));
await editButton.click();
expect(spectator.inject(IxSlideInService).open).toHaveBeenCalledWith(ServiceLldpComponent);
});
});

describe('view sessions', () => {
it('should navigate to view Sessions for SMB when Sessions button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="sessions-SMB"]' }));
expect(await (await editButton.host()).getAttribute('href')).toBe('/sharing/smb/status/sessions');
});

it('should navigate to view Sessions for NFS when Sessions button is pressed', async () => {
const editButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="sessions-NFS"]' }));
expect(await (await editButton.host()).getAttribute('href')).toBe('/sharing/nfs/sessions');
});
});

it('should change service enable state when slide is checked', async () => {
const table = await loader.getHarness(IxTableHarness);
const firstRow = await table.getFirstRow();
const serviceKey = [...serviceNames.entries()].find(([, value]) => value === firstRow.name)[0];

Expand All @@ -196,4 +189,10 @@ describe('ServicesComponent', () => {
expect(await checkbox.isChecked()).toBeTruthy();
expect(ws.call).toHaveBeenCalledWith('service.update', [0, { enable: true }]);
});

it('should show audit log icon for SMB service', async () => {
const logsButton = await table.getHarness(MatButtonHarness.with({ selector: '[name="logs-SMB"]' }));
const url = await (await logsButton.host()).getAttribute('href');
expect(decodeURI(url)).toBe('/system/audit/{"searchQuery":{"isBasicQuery":false,"filters":[["service","%3D","SMB"]]}}');
});
});
Loading

0 comments on commit 74ce255

Please sign in to comment.