diff --git a/src/app/core/testing/classes/mock-websocket.service.ts b/src/app/core/testing/classes/mock-websocket.service.ts index e9a6b2ff2d8..9ed4831bff9 100644 --- a/src/app/core/testing/classes/mock-websocket.service.ts +++ b/src/app/core/testing/classes/mock-websocket.service.ts @@ -10,7 +10,7 @@ import { } from 'app/core/testing/interfaces/mock-websocket-responses.interface'; import { ApiCallDirectory, ApiCallMethod, ApiCallParams } from 'app/interfaces/api/api-call-directory.interface'; import { ApiEventDirectory } from 'app/interfaces/api/api-event-directory.interface'; -import { ApiJobDirectory, ApiJobMethod } from 'app/interfaces/api/api-job-directory.interface'; +import { ApiJobDirectory, ApiJobMethod, ApiJobParams } from 'app/interfaces/api/api-job-directory.interface'; import { ApiEvent } from 'app/interfaces/api-message.interface'; import { Job } from 'app/interfaces/job.interface'; import { WebsocketConnectionService } from 'app/services/websocket-connection.service'; @@ -59,9 +59,9 @@ export class MockWebsocketService extends WebSocketService { } mockCall(method: K, response: CallResponseOrFactory): void { - const mockedImplementation = (): Observable => { + const mockedImplementation = (_: K, params: ApiCallParams): Observable => { if (response instanceof Function) { - return of(response()); + return of(response(params)); } return of(response); @@ -79,10 +79,10 @@ export class MockWebsocketService extends WebSocketService { .mockReturnValueOnce(of(response)); } mockJob(method: K, response: JobResponseOrFactory): void { - const getJobResponse = (): Job => { + const getJobResponse = (params: ApiJobParams = undefined): Job => { let job: Job; if (response instanceof Function) { - job = response(); + job = response(params); } else { job = response; } @@ -95,7 +95,8 @@ export class MockWebsocketService extends WebSocketService { when(this.startJob).calledWith(method).mockReturnValue(of(this.jobIdCounter)); when(this.startJob).calledWith(method, anyArgument).mockReturnValue(of(this.jobIdCounter)); when(this.job).calledWith(method).mockImplementation(() => of(getJobResponse())); - when(this.job).calledWith(method, anyArgument).mockImplementation(() => of(getJobResponse())); + when(this.job).calledWith(method, anyArgument) + .mockImplementation((_, params) => of(getJobResponse(params))); when(this.call) .calledWith('core.get_jobs', [[['id', '=', this.jobIdCounter]]]) .mockImplementation(() => of([getJobResponse()])); diff --git a/src/app/core/testing/interfaces/mock-websocket-responses.interface.ts b/src/app/core/testing/interfaces/mock-websocket-responses.interface.ts index b9688028729..c2a664c0e11 100644 --- a/src/app/core/testing/interfaces/mock-websocket-responses.interface.ts +++ b/src/app/core/testing/interfaces/mock-websocket-responses.interface.ts @@ -1,5 +1,5 @@ -import { ApiCallDirectory, ApiCallMethod } from 'app/interfaces/api/api-call-directory.interface'; -import { ApiJobDirectory, ApiJobMethod } from 'app/interfaces/api/api-job-directory.interface'; +import { ApiCallDirectory, ApiCallMethod, ApiCallParams } from 'app/interfaces/api/api-call-directory.interface'; +import { ApiJobDirectory, ApiJobMethod, ApiJobParams } from 'app/interfaces/api/api-job-directory.interface'; import { Job } from 'app/interfaces/job.interface'; export enum MockWebsocketResponseType { @@ -18,8 +18,13 @@ export interface MockWebsocketCallResponse { export interface MockWebsocketJobResponse { type: MockWebsocketResponseType.Job; method: ApiJobMethod; - response: Job | (() => Job); + response: Job | ((params: unknown) => Job); id?: number; } -export type CallResponseOrFactory = ApiCallDirectory[M]['response'] | (() => ApiCallDirectory[M]['response']); -export type JobResponseOrFactory = Job | (() => Job); +export type CallResponseOrFactory = + | ApiCallDirectory[M]['response'] + | ((params: ApiCallParams) => ApiCallDirectory[M]['response']); + +export type JobResponseOrFactory = + | Job + | ((params: ApiJobParams) => Job); diff --git a/src/app/enums/audit-event.enum.ts b/src/app/enums/audit-event.enum.ts deleted file mode 100644 index 7ea406ed2b5..00000000000 --- a/src/app/enums/audit-event.enum.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { marker as T } from '@biesbjerg/ngx-translate-extract-marker'; - -export enum AuditService { - Smb = 'SMB', - Middleware = 'MIDDLEWARE', -} - -export enum AuditEvent { - Connect = 'CONNECT', - Disconnect = 'DISCONNECT', - Create = 'CREATE', - Close = 'CLOSE', - Read = 'READ', - Write = 'WRITE', - OffloadRead = 'OFFLOAD_READ', - OffloadWrite = 'OFFLOAD_WRITE', - SetAcl = 'SET_ACL', - Rename = 'RENAME', - Unlink = 'UNLINK', - SetAttr = 'SET_ATTR', - SetQuota = 'SET_QUOTA', - Authentication = 'AUTHENTICATION', - MethodCall = 'METHOD_CALL', -} - -export const auditServiceLabels = new Map([ - [AuditService.Smb, T('SMB')], - [AuditService.Middleware, T('Middleware')], -]); - -export const auditEventLabels = new Map([ - [AuditEvent.Connect, T('Connect')], - [AuditEvent.Disconnect, T('Disconnect')], - [AuditEvent.Create, T('Create')], - [AuditEvent.Close, T('Close')], - [AuditEvent.Read, T('Read')], - [AuditEvent.Write, T('Write')], - [AuditEvent.OffloadRead, T('Offload Read')], - [AuditEvent.OffloadWrite, T('Offload Write')], - [AuditEvent.SetAcl, T('Set ACL')], - [AuditEvent.Rename, T('Rename')], - [AuditEvent.Unlink, T('Unlink')], - [AuditEvent.SetAttr, T('Set Attribute')], - [AuditEvent.SetQuota, T('Set Quota')], - [AuditEvent.Authentication, T('Authentication')], - [AuditEvent.MethodCall, T('Method Call')], -]); diff --git a/src/app/enums/audit.enum.ts b/src/app/enums/audit.enum.ts index 32ae3573428..7ea406ed2b5 100644 --- a/src/app/enums/audit.enum.ts +++ b/src/app/enums/audit.enum.ts @@ -1,4 +1,47 @@ +import { marker as T } from '@biesbjerg/ngx-translate-extract-marker'; + export enum AuditService { Smb = 'SMB', Middleware = 'MIDDLEWARE', } + +export enum AuditEvent { + Connect = 'CONNECT', + Disconnect = 'DISCONNECT', + Create = 'CREATE', + Close = 'CLOSE', + Read = 'READ', + Write = 'WRITE', + OffloadRead = 'OFFLOAD_READ', + OffloadWrite = 'OFFLOAD_WRITE', + SetAcl = 'SET_ACL', + Rename = 'RENAME', + Unlink = 'UNLINK', + SetAttr = 'SET_ATTR', + SetQuota = 'SET_QUOTA', + Authentication = 'AUTHENTICATION', + MethodCall = 'METHOD_CALL', +} + +export const auditServiceLabels = new Map([ + [AuditService.Smb, T('SMB')], + [AuditService.Middleware, T('Middleware')], +]); + +export const auditEventLabels = new Map([ + [AuditEvent.Connect, T('Connect')], + [AuditEvent.Disconnect, T('Disconnect')], + [AuditEvent.Create, T('Create')], + [AuditEvent.Close, T('Close')], + [AuditEvent.Read, T('Read')], + [AuditEvent.Write, T('Write')], + [AuditEvent.OffloadRead, T('Offload Read')], + [AuditEvent.OffloadWrite, T('Offload Write')], + [AuditEvent.SetAcl, T('Set ACL')], + [AuditEvent.Rename, T('Rename')], + [AuditEvent.Unlink, T('Unlink')], + [AuditEvent.SetAttr, T('Set Attribute')], + [AuditEvent.SetQuota, T('Set Quota')], + [AuditEvent.Authentication, T('Authentication')], + [AuditEvent.MethodCall, T('Method Call')], +]); diff --git a/src/app/interfaces/api/api-call-directory.interface.ts b/src/app/interfaces/api/api-call-directory.interface.ts index d7b64bf8c8d..5b1f8811363 100644 --- a/src/app/interfaces/api/api-call-directory.interface.ts +++ b/src/app/interfaces/api/api-call-directory.interface.ts @@ -28,7 +28,7 @@ import { import { ApiTimestamp } from 'app/interfaces/api-date.interface'; import { ApiKey, CreateApiKeyRequest, UpdateApiKeyRequest } from 'app/interfaces/api-key.interface'; import { UpgradeSummary } from 'app/interfaces/application.interface'; -import { AuditConfig, AuditEntry } from 'app/interfaces/audit/audit.interface'; +import { AuditConfig, AuditEntry, AuditQueryParams } from 'app/interfaces/audit/audit.interface'; import { AuthSession } from 'app/interfaces/auth-session.interface'; import { LoginQuery } from 'app/interfaces/auth.interface'; import { AvailableApp } from 'app/interfaces/available-app.interface'; @@ -314,7 +314,7 @@ export interface ApiCallDirectory { 'app.latest': { params: QueryParams; response: AvailableApp[] }; // Audit - 'audit.query': { params: QueryParams; response: AuditEntry[] }; + 'audit.query': { params: [AuditQueryParams]; response: AuditEntry[] }; 'audit.update': { params: [AuditConfig]; response: AuditEntry[] }; 'audit.config': { params: void; response: AuditConfig }; diff --git a/src/app/interfaces/audit/audit.interface.ts b/src/app/interfaces/audit/audit.interface.ts index 4dfbb3445ad..d4ec33bab3e 100644 --- a/src/app/interfaces/audit/audit.interface.ts +++ b/src/app/interfaces/audit/audit.interface.ts @@ -1,7 +1,14 @@ -import { AuditEvent } from 'app/enums/audit-event.enum'; +import { AuditEvent, AuditService } from 'app/enums/audit.enum'; import { ApiTimestamp } from 'app/interfaces/api-date.interface'; import { MiddlewareAuditEntry } from 'app/interfaces/audit/middleware-audit-entry.interface'; import { SmbAuditEntry } from 'app/interfaces/audit/smb-audit-entry.interface'; +import { QueryFilters, QueryOptions } from 'app/interfaces/query-api.interface'; + +export interface AuditQueryParams { + services?: AuditService[]; + 'query-filters'?: QueryFilters; + 'query-options'?: QueryOptions; +} export interface BaseAuditEntry { audit_id: string; diff --git a/src/app/interfaces/audit/middleware-audit-entry.interface.ts b/src/app/interfaces/audit/middleware-audit-entry.interface.ts index 389d9591814..d76fdd6489f 100644 --- a/src/app/interfaces/audit/middleware-audit-entry.interface.ts +++ b/src/app/interfaces/audit/middleware-audit-entry.interface.ts @@ -1,5 +1,4 @@ -import { AuditEvent } from 'app/enums/audit-event.enum'; -import { AuditService } from 'app/enums/audit.enum'; +import { AuditEvent, AuditService } from 'app/enums/audit.enum'; import { AuditVersions, BaseAuditEntry } from 'app/interfaces/audit/audit.interface'; import { CredentialType } from 'app/interfaces/credential-type.interface'; diff --git a/src/app/interfaces/audit/smb-audit-entry.interface.ts b/src/app/interfaces/audit/smb-audit-entry.interface.ts index f7138292c09..f355bee79ca 100644 --- a/src/app/interfaces/audit/smb-audit-entry.interface.ts +++ b/src/app/interfaces/audit/smb-audit-entry.interface.ts @@ -1,5 +1,4 @@ -import { AuditEvent } from 'app/enums/audit-event.enum'; -import { AuditService } from 'app/enums/audit.enum'; +import { AuditEvent, AuditService } from 'app/enums/audit.enum'; import { AuditVersions, BaseAuditEntry } from 'app/interfaces/audit/audit.interface'; export interface BaseSmbAuditEntry extends BaseAuditEntry { diff --git a/src/app/modules/ix-table2/components/ix-table2/ix-table2.harness.ts b/src/app/modules/ix-table2/components/ix-table2/ix-table2.harness.ts index 5b9bf82bdac..6298c74a2ac 100644 --- a/src/app/modules/ix-table2/components/ix-table2/ix-table2.harness.ts +++ b/src/app/modules/ix-table2/components/ix-table2/ix-table2.harness.ts @@ -116,6 +116,10 @@ export class IxTable2Harness extends ContentContainerComponentHarness { const texts = await parallel(() => cells.map((cell) => cell.getText())); const columnCount = await this.getColumnCount(); + if (!columnCount) { + throw new Error('Could not determine column count'); + } + const result: string[][] = []; for (let i = 0; i < texts.length; i += columnCount) { result.push(texts.slice(i, i + columnCount)); diff --git a/src/app/modules/search-input/components/advanced-search/advanced-search.component.spec.ts b/src/app/modules/search-input/components/advanced-search/advanced-search.component.spec.ts index 19d96c60aef..c86be508529 100644 --- a/src/app/modules/search-input/components/advanced-search/advanced-search.component.spec.ts +++ b/src/app/modules/search-input/components/advanced-search/advanced-search.component.spec.ts @@ -1,6 +1,6 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { auditEventLabels, AuditService } from 'app/enums/audit-event.enum'; +import { auditEventLabels, AuditService } from 'app/enums/audit.enum'; import { QueryFilters } from 'app/interfaces/query-api.interface'; import { User } from 'app/interfaces/user.interface'; import { AdvancedSearchComponent } from 'app/modules/search-input/components/advanced-search/advanced-search.component'; diff --git a/src/app/modules/search-input/services/query-to-api/query-to-api.service.spec.ts b/src/app/modules/search-input/services/query-to-api/query-to-api.service.spec.ts index b6ec2746f7b..05dbba54a31 100644 --- a/src/app/modules/search-input/services/query-to-api/query-to-api.service.spec.ts +++ b/src/app/modules/search-input/services/query-to-api/query-to-api.service.spec.ts @@ -1,6 +1,6 @@ import { TranslateService } from '@ngx-translate/core'; import { of } from 'rxjs'; -import { AuditService } from 'app/enums/audit-event.enum'; +import { AuditService } from 'app/enums/audit.enum'; import { IxFormatterService } from 'app/modules/ix-forms/services/ix-formatter.service'; import { ConnectorType, diff --git a/src/app/pages/audit/components/audit/audit.component.html b/src/app/pages/audit/components/audit/audit.component.html index f76a1aa19d1..8ca91125613 100644 --- a/src/app/pages/audit/components/audit/audit.component.html +++ b/src/app/pages/audit/components/audit/audit.component.html @@ -24,10 +24,10 @@ diff --git a/src/app/pages/audit/components/audit/audit.component.spec.ts b/src/app/pages/audit/components/audit/audit.component.spec.ts new file mode 100644 index 00000000000..816a10ec7f1 --- /dev/null +++ b/src/app/pages/audit/components/audit/audit.component.spec.ts @@ -0,0 +1,157 @@ +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { provideMockStore } from '@ngrx/store/testing'; +import { MockComponents } from 'ng-mocks'; +import { mockCall, mockWebsocket } from 'app/core/testing/utils/mock-websocket.utils'; +import { AuditService, AuditEvent } from 'app/enums/audit.enum'; +import { AuditEntry } from 'app/interfaces/audit/audit.interface'; +import { ExportButtonComponent } from 'app/modules/export-button/components/export-button/export-button.component'; +import { IxTable2Harness } from 'app/modules/ix-table2/components/ix-table2/ix-table2.harness'; +import { IxTable2Module } from 'app/modules/ix-table2/ix-table2.module'; +import { FakeProgressBarComponent } from 'app/modules/loader/components/fake-progress-bar/fake-progress-bar.component'; +import { SearchInputComponent } from 'app/modules/search-input/components/search-input/search-input.component'; +import { SearchInputModule } from 'app/modules/search-input/search-input.module'; +import { AuditComponent } from 'app/pages/audit/components/audit/audit.component'; +import { LogDetailsPanelComponent } from 'app/pages/audit/components/log-details-panel/log-details-panel.component'; +import { WebSocketService } from 'app/services/ws.service'; +import { selectTimezone } from 'app/store/system-config/system-config.selectors'; + +describe('AuditComponent', () => { + let spectator: Spectator; + let websocket: WebSocketService; + let table: IxTable2Harness; + + const auditEntries = [ + { + audit_id: '1', + timestamp: { + $date: 1702890820000, + }, + address: '10.220.2.21', + username: 'Administrator', + service: AuditService.Smb, + event: AuditEvent.Authentication, + event_data: { + clientAccount: 'Administrator', + }, + }, + { + audit_id: '2', + timestamp: { + $date: 1702894523000, + }, + address: '10.220.2.21', + username: 'bob', + service: AuditService.Smb, + event: AuditEvent.Create, + event_data: { + file_type: 'FILE', + file: { + path: 'test.txt', + }, + }, + }, + ] as AuditEntry[]; + + const createComponent = createComponentFactory({ + component: AuditComponent, + imports: [ + SearchInputModule, + IxTable2Module, + ], + declarations: [ + MockComponents( + LogDetailsPanelComponent, + ExportButtonComponent, + FakeProgressBarComponent, + ), + ], + providers: [ + mockWebsocket([ + mockCall('audit.query', (params) => { + if (params[0]['query-options'].count) { + // TODO: Not correct. Figure out how to solve this for query endpoints. + return 2 as unknown as AuditEntry[]; + } + + return auditEntries; + }), + mockCall('user.query', []), + ]), + provideMockStore({ + selectors: [ + { + selector: selectTimezone, + value: 'UTC', + }, + ], + }), + ], + }); + + beforeEach(async () => { + spectator = createComponent(); + websocket = spectator.inject(WebSocketService); + // Do it in this weird way because table header is outside the table element. + table = await TestbedHarnessEnvironment.harnessForFixture(spectator.fixture, IxTable2Harness); + }); + + it('loads and shows a table with audit entries', async () => { + expect(websocket.call).toHaveBeenCalledWith( + 'audit.query', + [{ 'query-filters': [], 'query-options': { limit: 50, offset: 0 } }], + ); + + await spectator.fixture.whenStable(); + await spectator.fixture.whenRenderingDone(); + expect(await table.getCellTexts()).toEqual([ + ['Service', 'User', 'Timestamp', 'Event', 'Event Data'], + ['SMB', 'Administrator', '2023-12-18 09:13:40', 'Authentication', 'Account: Administrator'], + ['SMB', 'bob', '2023-12-18 10:15:23', 'Create', 'File: test.txt'], + ]); + }); + + it('searches by event, username and service when basic search is used', () => { + const search = spectator.query(SearchInputComponent); + search.query = { + isBasicQuery: true, + query: 'search', + }; + search.runSearch.emit(); + + expect(websocket.call).toHaveBeenLastCalledWith( + 'audit.query', + [{ + 'query-filters': [['OR', [['event', '~', '(?i)search'], ['username', '~', '(?i)search'], ['service', '~', '(?i)search']]]], + 'query-options': { limit: 50, offset: 0 }, + }], + ); + }); + + it('applies filters to API query when advanced search is used', () => { + const search = spectator.query>(SearchInputComponent); + search.query = { + isBasicQuery: false, + filters: [ + ['event', '=', 'Authentication'], + ['username', '~', 'bob'], + ], + }; + search.runSearch.emit(); + + expect(websocket.call).toHaveBeenLastCalledWith( + 'audit.query', + [{ + 'query-filters': [['event', '=', 'Authentication'], ['username', '~', 'bob']], + 'query-options': { limit: 50, offset: 0 }, + }], + ); + }); + + it('shows details for the selected audit entry', async () => { + await table.clickRow(1); + + const details = spectator.query(LogDetailsPanelComponent); + expect(details.log).toEqual(auditEntries[1]); + }); +}); diff --git a/src/app/pages/audit/components/audit/audit.component.ts b/src/app/pages/audit/components/audit/audit.component.ts index 2a307a5ea34..02e467ca420 100644 --- a/src/app/pages/audit/components/audit/audit.component.ts +++ b/src/app/pages/audit/components/audit/audit.component.ts @@ -1,19 +1,21 @@ import { BreakpointObserver, BreakpointState, Breakpoints } from '@angular/cdk/layout'; import { - AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { TranslateService } from '@ngx-translate/core'; import { toSvg } from 'jdenticon'; -import { filter, map, of } from 'rxjs'; +import { + filter, map, of, shareReplay, +} from 'rxjs'; import { AuditEvent, AuditService, auditEventLabels, auditServiceLabels, -} from 'app/enums/audit-event.enum'; +} from 'app/enums/audit.enum'; import { ParamsBuilder } from 'app/helpers/params-builder/params-builder.class'; import { WINDOW } from 'app/helpers/window.helper'; -import { AuditEntry } from 'app/interfaces/audit/audit.interface'; +import { AuditEntry, AuditQueryParams } from 'app/interfaces/audit/audit.interface'; import { CredentialType, credentialTypeLabels } from 'app/interfaces/credential-type.interface'; import { QueryFilters } from 'app/interfaces/query-api.interface'; import { ApiDataProvider } from 'app/modules/ix-table2/classes/api-data-provider/api-data-provider'; @@ -45,7 +47,7 @@ import { WebSocketService } from 'app/services/ws.service'; styleUrls: ['./audit.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class AuditComponent implements OnInit, AfterViewInit, OnDestroy { +export class AuditComponent implements OnInit, OnDestroy { protected dataProvider: ApiDataProvider; showMobileDetails = false; isMobileView = false; @@ -62,7 +64,7 @@ export class AuditComponent implements OnInit, AfterViewInit, OnDestroy { propertyName: 'service', getValue: (row) => (auditServiceLabels.has(row.service) ? this.translate.instant(auditServiceLabels.get(row.service)) - : row.event || '-'), + : row.service || '-'), }), textColumn({ title: this.translate.instant('User'), @@ -87,6 +89,14 @@ export class AuditComponent implements OnInit, AfterViewInit, OnDestroy { protected searchProperties: SearchProperty[] = []; + private userSuggestions = this.ws.call('user.query').pipe( + map((users) => users.map((user) => ({ + label: user.username, + value: `"${user.username}"`, + }))), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + constructor( private translate: TranslateService, private ws: WebSocketService, @@ -108,95 +118,7 @@ export class AuditComponent implements OnInit, AfterViewInit, OnDestroy { this.dataProvider.expandedRow = this.isMobileView ? null : auditEntries[0]; this.expanded(this.dataProvider.expandedRow); - this.searchProperties = searchProperties([ - textProperty('audit_id', this.translate.instant('ID'), of([])), - dateProperty( - 'message_timestamp', - this.translate.instant('Timestamp'), - ), - textProperty( - 'address', - this.translate.instant('Address'), - of(auditEntries.map((log) => ({ - label: log.address, - value: `"${log.address}"`, - }))), - ), - textProperty( - 'service', - this.translate.instant('Service'), - of(Object.values(AuditService).map((key) => ({ - label: this.translate.instant(auditServiceLabels.get(key)), - value: `"${this.translate.instant(auditServiceLabels.get(key))}"`, - }))), - auditServiceLabels, - ), - textProperty( - 'username', - this.translate.instant('Username'), - this.ws.call('user.query').pipe(( - map((users) => users.map((user) => ({ - label: user.username, - value: `"${user.username}"`, - }))) - )), - ), - textProperty( - 'event', - this.translate.instant('Event'), - of(Object.values(AuditEvent).map((key) => ({ - label: this.translate.instant(auditEventLabels.get(key)), - value: `"${this.translate.instant(auditEventLabels.get(key))}"`, - }))), - auditEventLabels, - ), - textProperty( - 'event_data.clientAccount', - this.translate.instant('SMB - Client Account'), - this.ws.call('user.query').pipe(( - map((users) => users.map((user) => ({ - label: user.username, - value: `"${user.username}"`, - }))) - )), - ), - textProperty('event_data.host', this.translate.instant('SMB - Host')), - textProperty('event_data.file.path', this.translate.instant('SMB - File Path')), - textProperty('event_data.src_file.path', this.translate.instant('SMB - Source File Path')), - textProperty('event_data.dst_file.path', this.translate.instant('SMB - Destination File Path')), - textProperty('event_data.file.handle.type', this.translate.instant('SMB - File Handle Type')), - textProperty('event_data.file.handle.value', this.translate.instant('SMB - File Handle Value')), - textProperty('event_data.unix_token.uid', this.translate.instant('SMB - UNIX Token UID')), - textProperty('event_data.unix_token.gid', this.translate.instant('SMB - UNIX Token GID')), - textProperty('event_data.unix_token.groups', this.translate.instant('SMB - UNIX Token Groups')), - textProperty('event_data.result.type', this.translate.instant('SMB - Result Type')), - textProperty('event_data.result.value_raw', this.translate.instant('SMB - Result Raw Value')), - textProperty('event_data.result.value_parsed', this.translate.instant('SMB - Result Parsed Value')), - textProperty( - 'event_data.vers.major', - this.translate.instant('SMB - Vers Major'), - of([{ label: '0', value: 0 }, { label: '1', value: 1 }]), - ), - textProperty( - 'event_data.vers.minor', - this.translate.instant('SMB - Vers Minor'), - of([{ label: '0', value: 0 }, { label: '1', value: 1 }]), - ), - textProperty('event_data.operations.create', this.translate.instant('SMB - Operation Create')), - textProperty('event_data.operations.close', this.translate.instant('SMB - Operation Close')), - textProperty('event_data.operations.read', this.translate.instant('SMB - Operation Read')), - textProperty('event_data.operations.write', this.translate.instant('SMB - Operation Write')), - textProperty( - 'event_data.credentials.credentials', - this.translate.instant('Middleware - Credentials'), - of(Object.values(CredentialType).map((key) => ({ - label: this.translate.instant(credentialTypeLabels.get(key)), - value: `"${this.translate.instant(credentialTypeLabels.get(key))}"`, - }))), - credentialTypeLabels, - ), - textProperty('event_data.method', this.translate.instant('Middleware - Method')), - ]); + this.setSearchProperties(auditEntries); this.cdr.markForCheck(); }); @@ -204,38 +126,8 @@ export class AuditComponent implements OnInit, AfterViewInit, OnDestroy { this.updateUrlOptions(); }); - this.activatedRoute.params.pipe(untilDestroyed(this)).subscribe((params) => { - const options = this.urlOptionsService.parseUrlOptions(params.options); - - this.pagination = { - pageSize: options.pagination?.pageSize || 50, - pageNumber: options.pagination?.pageNumber || 1, - }; - - if (options.sorting) this.dataProvider.setSorting(options.sorting); - if (options.searchQuery) this.searchQuery = options.searchQuery as SearchQuery; - - this.onSearch(this.searchQuery); - }); - } - - ngAfterViewInit(): void { - this.breakpointObserver - .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium]) - .pipe(untilDestroyed(this)) - .subscribe((state: BreakpointState) => { - if (state.matches) { - this.isMobileView = true; - if (this.dataProvider.expandedRow) { - this.expanded(this.dataProvider.expandedRow); - } else { - this.closeMobileDetails(); - } - } else { - this.isMobileView = false; - } - this.cdr.markForCheck(); - }); + this.loadParamsFromRoute(); + this.initMobileView(); } ngOnDestroy(): void { @@ -257,11 +149,15 @@ export class AuditComponent implements OnInit, AfterViewInit, OnDestroy { .orFilter('service', '~', term) .getParams(); - this.dataProvider.setParams(params); + // TODO: Incorrect cast, because of incorrect typing inside of DataProvider + this.dataProvider.setParams(params as unknown as [AuditQueryParams]); } if (query && !query.isBasicQuery) { - this.dataProvider.setParams([(query as AdvancedSearchQuery).filters]); + // TODO: Incorrect cast, because of incorrect typing inside of DataProvider + this.dataProvider.setParams( + [(query as AdvancedSearchQuery).filters] as unknown as [AuditQueryParams], + ); } this.dataProvider.load(); @@ -299,6 +195,123 @@ export class AuditComponent implements OnInit, AfterViewInit, OnDestroy { return this.sanitizer.bypassSecurityTrustHtml(toSvg(`${row.username}`, 35)); } + private initMobileView(): void { + this.breakpointObserver + .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium]) + .pipe(untilDestroyed(this)) + .subscribe((state: BreakpointState) => { + if (state.matches) { + this.isMobileView = true; + if (this.dataProvider.expandedRow) { + this.expanded(this.dataProvider.expandedRow); + } else { + this.closeMobileDetails(); + } + } else { + this.isMobileView = false; + } + this.cdr.markForCheck(); + }); + } + + private setSearchProperties(auditEntries: AuditEntry[]): void { + this.searchProperties = searchProperties([ + textProperty('audit_id', this.translate.instant('ID'), of([])), + dateProperty( + 'message_timestamp', + this.translate.instant('Timestamp'), + ), + textProperty( + 'address', + this.translate.instant('Address'), + of(auditEntries.map((log) => ({ + label: log.address, + value: `"${log.address}"`, + }))), + ), + textProperty( + 'service', + this.translate.instant('Service'), + of(Object.values(AuditService).map((key) => ({ + label: this.translate.instant(auditServiceLabels.get(key)), + value: `"${this.translate.instant(auditServiceLabels.get(key))}"`, + }))), + auditServiceLabels, + ), + textProperty( + 'username', + this.translate.instant('Username'), + this.userSuggestions, + ), + textProperty( + 'event', + this.translate.instant('Event'), + of(Object.values(AuditEvent).map((key) => ({ + label: this.translate.instant(auditEventLabels.get(key)), + value: `"${this.translate.instant(auditEventLabels.get(key))}"`, + }))), + auditEventLabels, + ), + textProperty( + 'event_data.clientAccount', + this.translate.instant('SMB - Client Account'), + this.userSuggestions, + ), + textProperty('event_data.host', this.translate.instant('SMB - Host')), + textProperty('event_data.file.path', this.translate.instant('SMB - File Path')), + textProperty('event_data.src_file.path', this.translate.instant('SMB - Source File Path')), + textProperty('event_data.dst_file.path', this.translate.instant('SMB - Destination File Path')), + textProperty('event_data.file.handle.type', this.translate.instant('SMB - File Handle Type')), + textProperty('event_data.file.handle.value', this.translate.instant('SMB - File Handle Value')), + textProperty('event_data.unix_token.uid', this.translate.instant('SMB - UNIX Token UID')), + textProperty('event_data.unix_token.gid', this.translate.instant('SMB - UNIX Token GID')), + textProperty('event_data.unix_token.groups', this.translate.instant('SMB - UNIX Token Groups')), + textProperty('event_data.result.type', this.translate.instant('SMB - Result Type')), + textProperty('event_data.result.value_raw', this.translate.instant('SMB - Result Raw Value')), + textProperty('event_data.result.value_parsed', this.translate.instant('SMB - Result Parsed Value')), + textProperty( + 'event_data.vers.major', + this.translate.instant('SMB - Vers Major'), + of([{ label: '0', value: 0 }, { label: '1', value: 1 }]), + ), + textProperty( + 'event_data.vers.minor', + this.translate.instant('SMB - Vers Minor'), + of([{ label: '0', value: 0 }, { label: '1', value: 1 }]), + ), + textProperty('event_data.operations.create', this.translate.instant('SMB - Operation Create')), + textProperty('event_data.operations.close', this.translate.instant('SMB - Operation Close')), + textProperty('event_data.operations.read', this.translate.instant('SMB - Operation Read')), + textProperty('event_data.operations.write', this.translate.instant('SMB - Operation Write')), + textProperty( + 'event_data.credentials.credentials', + this.translate.instant('Middleware - Credentials'), + of(Object.values(CredentialType).map((key) => ({ + label: this.translate.instant(credentialTypeLabels.get(key)), + value: `"${this.translate.instant(credentialTypeLabels.get(key))}"`, + }))), + credentialTypeLabels, + ), + textProperty('event_data.method', this.translate.instant('Middleware - Method')), + ]); + } + + private loadParamsFromRoute(): void { + this.activatedRoute.params.pipe(untilDestroyed(this)).subscribe((params) => { + const options = this.urlOptionsService.parseUrlOptions(params.options); + + this.pagination = { + pageSize: options.pagination?.pageSize || 50, + pageNumber: options.pagination?.pageNumber || 1, + }; + + if (options.sorting) this.dataProvider.setSorting(options.sorting); + if (options.searchQuery) this.searchQuery = options.searchQuery as SearchQuery; + + this.onSearch(this.searchQuery); + }); + } + private getEventDataForLog(row: AuditEntry): string { return getLogImportantData(row, this.translate); } diff --git a/src/app/pages/audit/components/event-data-details-card/event-data-details-card.component.spec.ts b/src/app/pages/audit/components/event-data-details-card/event-data-details-card.component.spec.ts index 8e02b98a8d0..a65c5447a3c 100644 --- a/src/app/pages/audit/components/event-data-details-card/event-data-details-card.component.spec.ts +++ b/src/app/pages/audit/components/event-data-details-card/event-data-details-card.component.spec.ts @@ -1,8 +1,7 @@ import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { MockComponent } from 'ng-mocks'; import { CopyButtonComponent } from 'app/core/components/copy-btn/copy-btn.component'; -import { AuditEvent } from 'app/enums/audit-event.enum'; -import { AuditService } from 'app/enums/audit.enum'; +import { AuditEvent, AuditService } from 'app/enums/audit.enum'; import { AuditEntry } from 'app/interfaces/audit/audit.interface'; import { IxTable2Module } from 'app/modules/ix-table2/ix-table2.module'; import { diff --git a/src/app/pages/audit/components/metadata-details-card/metadata-details-card.component.spec.ts b/src/app/pages/audit/components/metadata-details-card/metadata-details-card.component.spec.ts index 86fd7692602..9b156074534 100644 --- a/src/app/pages/audit/components/metadata-details-card/metadata-details-card.component.spec.ts +++ b/src/app/pages/audit/components/metadata-details-card/metadata-details-card.component.spec.ts @@ -1,6 +1,5 @@ import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { AuditEvent } from 'app/enums/audit-event.enum'; -import { AuditService } from 'app/enums/audit.enum'; +import { AuditEvent, AuditService } from 'app/enums/audit.enum'; import { AuditEntry } from 'app/interfaces/audit/audit.interface'; import { IxTable2Module } from 'app/modules/ix-table2/ix-table2.module'; import { diff --git a/src/app/pages/audit/utils/get-log-important-data.utils.spec.ts b/src/app/pages/audit/utils/get-log-important-data.utils.spec.ts index 56aacffd626..a70e54f1d50 100644 --- a/src/app/pages/audit/utils/get-log-important-data.utils.spec.ts +++ b/src/app/pages/audit/utils/get-log-important-data.utils.spec.ts @@ -1,6 +1,5 @@ import { TranslateService } from '@ngx-translate/core'; -import { AuditEvent } from 'app/enums/audit-event.enum'; -import { AuditService } from 'app/enums/audit.enum'; +import { AuditEvent, AuditService } from 'app/enums/audit.enum'; import { AuditEntry } from 'app/interfaces/audit/audit.interface'; import { CredentialType } from 'app/interfaces/credential-type.interface'; import { getLogImportantData } from 'app/pages/audit/utils/get-log-important-data.utils'; diff --git a/src/app/pages/audit/utils/get-log-important-data.utils.ts b/src/app/pages/audit/utils/get-log-important-data.utils.ts index ac4650f2eeb..94e686b7d7d 100644 --- a/src/app/pages/audit/utils/get-log-important-data.utils.ts +++ b/src/app/pages/audit/utils/get-log-important-data.utils.ts @@ -1,7 +1,6 @@ import { marker as T } from '@biesbjerg/ngx-translate-extract-marker'; import { TranslateService } from '@ngx-translate/core'; -import { AuditEvent } from 'app/enums/audit-event.enum'; -import { AuditService } from 'app/enums/audit.enum'; +import { AuditEvent, AuditService } from 'app/enums/audit.enum'; import { assertUnreachable } from 'app/helpers/assert-unreachable.utils'; import { AuditEntry } from 'app/interfaces/audit/audit.interface'; import { MiddlewareAuditEntry } from 'app/interfaces/audit/middleware-audit-entry.interface';