42 changed files with 1125 additions and 35 deletions
@ -0,0 +1,59 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { defaultHttpOptions } from './http-utils'; |
|||
import { Observable } from 'rxjs/index'; |
|||
import { HttpClient } from '@angular/common/http'; |
|||
import { PageLink, TimePageLink } from '@shared/models/page/page-link'; |
|||
import { PageData } from '@shared/models/page/page-data'; |
|||
import { AuditLog } from '@shared/models/audit-log.models'; |
|||
import { EntityId } from '@shared/models/id/entity-id'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class AuditLogService { |
|||
|
|||
constructor( |
|||
private http: HttpClient |
|||
) { } |
|||
|
|||
public getAuditLogs(pageLink: TimePageLink, |
|||
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { |
|||
return this.http.get<PageData<AuditLog>>(`/api/audit/logs${pageLink.toQuery()}`, |
|||
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
public getAuditLogsByCustomerId(customerId: string, pageLink: TimePageLink, |
|||
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { |
|||
return this.http.get<PageData<AuditLog>>(`/api/audit/logs/customer/${customerId}${pageLink.toQuery()}`, |
|||
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
public getAuditLogsByUserId(userId: string, pageLink: TimePageLink, |
|||
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { |
|||
return this.http.get<PageData<AuditLog>>(`/api/audit/logs/user/${userId}${pageLink.toQuery()}`, |
|||
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
public getAuditLogsByEntityId(entityId: EntityId, pageLink: TimePageLink, |
|||
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AuditLog>> { |
|||
return this.http.get<PageData<AuditLog>>(`/api/audit/logs/entity/${entityId.entityType}/${entityId.id}${pageLink.toQuery()}`, |
|||
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2 translate>audit-log.audit-log-details</h2> |
|||
<span fxFlex></span> |
|||
<button mat-button mat-icon-button |
|||
[mat-dialog-close]="false" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<div mat-dialog-content fxLayout="column"> |
|||
<label translate class="tb-title no-padding">audit-log.action-data</label> |
|||
<div #actionDataEditor class="tb-audit-log-action-data"> |
|||
</div> |
|||
<span style="min-height: 30px;"></span> |
|||
<label [fxShow]="displayFailureDetails" translate class="tb-title no-padding">audit-log.failure-details</label> |
|||
<div #failureDetailsEditor [fxShow]="displayFailureDetails" class="tb-audit-log-failure-details"> |
|||
</div> |
|||
</div> |
|||
<div mat-dialog-actions fxLayout="row"> |
|||
<span fxFlex></span> |
|||
<button mat-button color="primary" |
|||
style="margin-right: 20px;" |
|||
type="button" |
|||
[disabled]="(isLoading$ | async)" |
|||
[mat-dialog-close]="false" cdkFocusInitial> |
|||
{{ 'action.close' | translate }} |
|||
</button> |
|||
</div> |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
.tb-audit-log-action-data, |
|||
.tb-audit-log-failure-details { |
|||
width: 100%; |
|||
min-width: 400px; |
|||
height: 100%; |
|||
min-height: 50px; |
|||
border: 1px solid #c0c0c0; |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
Component, |
|||
ElementRef, |
|||
Inject, |
|||
OnInit, |
|||
Renderer2, |
|||
ViewChild |
|||
} from '@angular/core'; |
|||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; |
|||
import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; |
|||
|
|||
import * as ace from 'ace-builds'; |
|||
|
|||
export interface AuditLogDetailsDialogData { |
|||
auditLog: AuditLog; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-audit-log-details-dialog', |
|||
templateUrl: './audit-log-details-dialog.component.html', |
|||
styleUrls: ['./audit-log-details-dialog.component.scss'] |
|||
}) |
|||
export class AuditLogDetailsDialogComponent extends PageComponent implements OnInit { |
|||
|
|||
@ViewChild('actionDataEditor', {static: true}) |
|||
actionDataEditorElmRef: ElementRef; |
|||
private actionDataEditor: ace.Ace.Editor; |
|||
|
|||
@ViewChild('failureDetailsEditor', {static: true}) |
|||
failureDetailsEditorElmRef: ElementRef; |
|||
private failureDetailsEditor: ace.Ace.Editor; |
|||
|
|||
auditLog: AuditLog; |
|||
displayFailureDetails: boolean; |
|||
actionData: string; |
|||
actionFailureDetails: string; |
|||
|
|||
@ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
@Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData, |
|||
public dialogRef: MatDialogRef<AuditLogDetailsDialogComponent>, |
|||
private renderer: Renderer2) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.auditLog = this.data.auditLog; |
|||
this.displayFailureDetails = this.auditLog.actionStatus === ActionStatus.FAILURE; |
|||
this.actionData = this.auditLog.actionData ? JSON.stringify(this.auditLog.actionData, null, 2) : ''; |
|||
this.actionFailureDetails = this.auditLog.actionFailureDetails; |
|||
|
|||
this.actionDataEditor = this.createEditor(this.actionDataEditorElmRef, this.actionData); |
|||
if (this.displayFailureDetails) { |
|||
this.failureDetailsEditor = this.createEditor(this.failureDetailsEditorElmRef, this.actionFailureDetails); |
|||
} |
|||
} |
|||
|
|||
createEditor(editorElementRef: ElementRef, content: string): ace.Ace.Editor { |
|||
const editorElement = editorElementRef.nativeElement; |
|||
let editorOptions: Partial<ace.Ace.EditorOptions> = { |
|||
mode: 'ace/mode/java', |
|||
theme: 'ace/theme/github', |
|||
showGutter: false, |
|||
showPrintMargin: false, |
|||
readOnly: true |
|||
}; |
|||
|
|||
const advancedOptions = { |
|||
enableSnippets: false, |
|||
enableBasicAutocompletion: false, |
|||
enableLiveAutocompletion: false |
|||
}; |
|||
|
|||
editorOptions = {...editorOptions, ...advancedOptions}; |
|||
const editor = ace.edit(editorElement, editorOptions); |
|||
editor.session.setUseWrapMode(false); |
|||
editor.setValue(content, -1); |
|||
this.updateEditorSize(editorElement, content, editor); |
|||
return editor; |
|||
} |
|||
|
|||
updateEditorSize(editorElement: any, content: string, editor: ace.Ace.Editor) { |
|||
let newHeight = 200; |
|||
let newWidth = 600; |
|||
if (content && content.length > 0) { |
|||
const lines = content.split('\n'); |
|||
newHeight = 16 * lines.length + 16; |
|||
let maxLineLength = 0; |
|||
lines.forEach((row) => { |
|||
const line = row.replace(/\t/g, ' ').replace(/\n/g, ''); |
|||
const lineLength = line.length; |
|||
maxLineLength = Math.max(maxLineLength, lineLength); |
|||
}); |
|||
newWidth = 8 * maxLineLength + 16; |
|||
} |
|||
newHeight = Math.min(400, newHeight); |
|||
this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); |
|||
this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); |
|||
this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); |
|||
editor.resize(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
DateEntityTableColumn, |
|||
EntityTableColumn, |
|||
EntityTableConfig |
|||
} from '@home/models/entity/entities-table-config.models'; |
|||
import { |
|||
actionStatusTranslations, |
|||
actionTypeTranslations, |
|||
AuditLog, |
|||
AuditLogMode |
|||
} from '@shared/models/audit-log.models'; |
|||
import { |
|||
EntityTypeResource, |
|||
EntityTypeTranslation, |
|||
entityTypeTranslations |
|||
} from '@shared/models/entity-type.models'; |
|||
import { AuditLogService } from '@core/http/audit-log.service'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { DatePipe } from '@angular/common'; |
|||
import { Direction } from '@shared/models/page/sort-order'; |
|||
import { MatDialog } from '@angular/material'; |
|||
import { PageLink, TimePageLink } from '@shared/models/page/page-link'; |
|||
import { Observable } from 'rxjs'; |
|||
import { PageData } from '@shared/models/page/page-data'; |
|||
import { EntityId } from '@shared/models/id/entity-id'; |
|||
import { UserId } from '@shared/models/id/user-id'; |
|||
import { CustomerId } from '@shared/models/id/customer-id'; |
|||
import { |
|||
AuditLogDetailsDialogComponent, |
|||
AuditLogDetailsDialogData |
|||
} from '@home/components/audit-log/audit-log-details-dialog.component'; |
|||
|
|||
export class AuditLogTableConfig extends EntityTableConfig<AuditLog, TimePageLink> { |
|||
|
|||
constructor(private auditLogService: AuditLogService, |
|||
private translate: TranslateService, |
|||
private datePipe: DatePipe, |
|||
private dialog: MatDialog, |
|||
private auditLogMode: AuditLogMode = AuditLogMode.TENANT, |
|||
public entityId: EntityId = null, |
|||
public userId: UserId = null, |
|||
public customerId: CustomerId = null, |
|||
updateOnInit = true) { |
|||
super(); |
|||
this.loadDataOnInit = updateOnInit; |
|||
this.tableTitle = ''; |
|||
this.useTimePageLink = true; |
|||
this.detailsPanelEnabled = false; |
|||
this.selectionEnabled = false; |
|||
this.searchEnabled = true; |
|||
this.addEnabled = false; |
|||
this.entitiesDeleteEnabled = false; |
|||
this.actionsColumnTitle = 'audit-log.details'; |
|||
this.entityTranslations = { |
|||
noEntities: 'audit-log.no-audit-logs-prompt', |
|||
search: 'audit-log.search' |
|||
}; |
|||
this.entityResources = { |
|||
} as EntityTypeResource; |
|||
|
|||
this.entitiesFetchFunction = pageLink => this.fetchAuditLogs(pageLink); |
|||
|
|||
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
|||
|
|||
this.columns.push( |
|||
new DateEntityTableColumn<AuditLog>('createdTime', 'audit-log.timestamp', this.datePipe, '150px')); |
|||
|
|||
if (this.auditLogMode !== AuditLogMode.ENTITY) { |
|||
this.columns.push( |
|||
new EntityTableColumn<AuditLog>('entityType', 'audit-log.entity-type', '100%', |
|||
(entity) => translate.instant(entityTypeTranslations.get(entity.entityId.entityType).type)), |
|||
new EntityTableColumn<AuditLog>('entityName', 'audit-log.entity-name'), |
|||
); |
|||
} |
|||
|
|||
if (this.auditLogMode !== AuditLogMode.USER) { |
|||
this.columns.push( |
|||
new EntityTableColumn<AuditLog>('userName', 'audit-log.user') |
|||
); |
|||
} |
|||
|
|||
this.columns.push( |
|||
new EntityTableColumn<AuditLog>('actionType', 'audit-log.type', '100%', |
|||
(entity) => translate.instant(actionTypeTranslations.get(entity.actionType))), |
|||
new EntityTableColumn<AuditLog>('actionStatus', 'audit-log.status', '100%', |
|||
(entity) => translate.instant(actionStatusTranslations.get(entity.actionStatus))) |
|||
); |
|||
|
|||
this.cellActionDescriptors.push( |
|||
{ |
|||
name: this.translate.instant('audit-log.details'), |
|||
icon: 'more_horiz', |
|||
isEnabled: () => true, |
|||
onAction: ($event, entity) => this.showAuditLogDetails(entity) |
|||
} |
|||
); |
|||
} |
|||
|
|||
fetchAuditLogs(pageLink: TimePageLink): Observable<PageData<AuditLog>> { |
|||
switch (this.auditLogMode) { |
|||
case AuditLogMode.TENANT: |
|||
return this.auditLogService.getAuditLogs(pageLink); |
|||
case AuditLogMode.ENTITY: |
|||
return this.auditLogService.getAuditLogsByEntityId(this.entityId, pageLink); |
|||
case AuditLogMode.USER: |
|||
return this.auditLogService.getAuditLogsByUserId(this.userId.id, pageLink); |
|||
case AuditLogMode.CUSTOMER: |
|||
return this.auditLogService.getAuditLogsByCustomerId(this.customerId.id, pageLink); |
|||
} |
|||
} |
|||
|
|||
showAuditLogDetails(entity: AuditLog) { |
|||
this.dialog.open<AuditLogDetailsDialogComponent, AuditLogDetailsDialogData>(AuditLogDetailsDialogComponent, { |
|||
disableClose: true, |
|||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
|||
data: { |
|||
auditLog: entity |
|||
} |
|||
}); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<tb-entities-table [entitiesTableConfig]="auditLogTableConfig" [ngClass]="{'tb-details-mode': detailsMode}"></tb-entities-table> |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* Copyright © 2016-2019 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host ::ng-deep { |
|||
tb-entities-table.tb-details-mode { |
|||
.mat-drawer-container { |
|||
background-color: white; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, Input, OnInit, ViewChild } from '@angular/core'; |
|||
import { AuditLogService } from '@core/http/audit-log.service'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { DatePipe } from '@angular/common'; |
|||
import { MatDialog } from '@angular/material'; |
|||
import { AuditLogMode } from '@shared/models/audit-log.models'; |
|||
import { EntityId } from '@shared/models/id/entity-id'; |
|||
import { UserId } from '@shared/models/id/user-id'; |
|||
import { CustomerId } from '@shared/models/id/customer-id'; |
|||
import { AuditLogTableConfig } from '@home/components/audit-log/audit-log-table-config'; |
|||
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-audit-log-table', |
|||
templateUrl: './audit-log-table.component.html', |
|||
styleUrls: ['./audit-log-table.component.scss'] |
|||
}) |
|||
export class AuditLogTableComponent implements OnInit { |
|||
|
|||
@Input() |
|||
auditLogMode: AuditLogMode; |
|||
|
|||
@Input() |
|||
detailsMode: boolean; |
|||
|
|||
activeValue = false; |
|||
dirtyValue = false; |
|||
entityIdValue: EntityId; |
|||
userIdValue: UserId; |
|||
customerIdValue: CustomerId; |
|||
|
|||
@Input() |
|||
set active(active: boolean) { |
|||
if (this.activeValue !== active) { |
|||
this.activeValue = active; |
|||
if (this.activeValue && this.dirtyValue) { |
|||
this.dirtyValue = false; |
|||
this.entitiesTable.updateData(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Input() |
|||
set entityId(entityId: EntityId) { |
|||
this.entityIdValue = entityId; |
|||
if (this.auditLogTableConfig && this.auditLogTableConfig.entityId !== entityId) { |
|||
this.auditLogTableConfig.entityId = entityId; |
|||
this.entitiesTable.resetSortAndFilter(this.activeValue); |
|||
if (!this.activeValue) { |
|||
this.dirtyValue = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Input() |
|||
set userId(userId: UserId) { |
|||
this.userIdValue = userId; |
|||
if (this.auditLogTableConfig && this.auditLogTableConfig.userId !== userId) { |
|||
this.auditLogTableConfig.userId = userId; |
|||
this.entitiesTable.resetSortAndFilter(this.activeValue); |
|||
if (!this.activeValue) { |
|||
this.dirtyValue = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Input() |
|||
set customerId(customerId: CustomerId) { |
|||
this.customerIdValue = customerId; |
|||
if (this.auditLogTableConfig && this.auditLogTableConfig.customerId !== customerId) { |
|||
this.auditLogTableConfig.customerId = customerId; |
|||
this.entitiesTable.resetSortAndFilter(this.activeValue); |
|||
if (!this.activeValue) { |
|||
this.dirtyValue = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; |
|||
|
|||
auditLogTableConfig: AuditLogTableConfig; |
|||
|
|||
constructor(private auditLogService: AuditLogService, |
|||
private translate: TranslateService, |
|||
private datePipe: DatePipe, |
|||
private dialog: MatDialog, |
|||
private store: Store<AppState>) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
let updateOnInit = this.activeValue; |
|||
this.dirtyValue = !this.activeValue; |
|||
if (!this.auditLogMode) { |
|||
const authUser = getCurrentAuthUser(this.store); |
|||
if (authUser.authority === Authority.TENANT_ADMIN) { |
|||
this.auditLogMode = AuditLogMode.TENANT; |
|||
} |
|||
updateOnInit = true; |
|||
} |
|||
this.auditLogTableConfig = new AuditLogTableConfig( |
|||
this.auditLogService, |
|||
this.translate, |
|||
this.datePipe, |
|||
this.dialog, |
|||
this.auditLogMode, |
|||
this.entityIdValue, |
|||
this.userIdValue, |
|||
this.customerIdValue, |
|||
updateOnInit |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
|||
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
|||
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> |
|||
</mat-tab> |
|||
@ -0,0 +1,38 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component } from '@angular/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; |
|||
import { AssetInfo } from '@app/shared/models/asset.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-asset-tabs', |
|||
templateUrl: './asset-tabs.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class AssetTabsComponent extends EntityTabsComponent<AssetInfo> { |
|||
|
|||
constructor(protected store: Store<AppState>) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
super.ngOnInit(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { RouterModule, Routes } from '@angular/router'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component'; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: 'auditLogs', |
|||
component: AuditLogTableComponent, |
|||
data: { |
|||
auth: [Authority.TENANT_ADMIN], |
|||
title: 'audit-log.audit-logs', |
|||
breadcrumb: { |
|||
label: 'audit-log.audit-logs', |
|||
icon: 'track_changes' |
|||
} |
|||
} |
|||
} |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class AuditLogRoutingModule { } |
|||
@ -0,0 +1,31 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { NgModule } from '@angular/core'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { AuditLogRoutingModule } from '@modules/home/pages/audit-log/audit-log-routing.module'; |
|||
|
|||
@NgModule({ |
|||
declarations: [ |
|||
], |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
AuditLogRoutingModule |
|||
] |
|||
}) |
|||
export class AuditLogModule { } |
|||
@ -0,0 +1,21 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
|||
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
|||
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.CUSTOMER" [customerId]="entity.id" detailsMode="true"></tb-audit-log-table> |
|||
</mat-tab> |
|||
@ -0,0 +1,38 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component } from '@angular/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; |
|||
import { Customer } from '@shared/models/customer.model'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-customer-tabs', |
|||
templateUrl: './customer-tabs.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class CustomerTabsComponent extends EntityTabsComponent<Customer> { |
|||
|
|||
constructor(protected store: Store<AppState>) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
super.ngOnInit(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
|||
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
|||
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> |
|||
</mat-tab> |
|||
@ -0,0 +1,38 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component } from '@angular/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; |
|||
import { Dashboard } from '@shared/models/dashboard.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-dashboard-tabs', |
|||
templateUrl: './dashboard-tabs.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class DashboardTabsComponent extends EntityTabsComponent<Dashboard> { |
|||
|
|||
constructor(protected store: Store<AppState>) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
super.ngOnInit(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
|||
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
|||
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> |
|||
</mat-tab> |
|||
@ -0,0 +1,38 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component } from '@angular/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; |
|||
import { EntityViewInfo } from '@app/shared/models/entity-view.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-entity-view-tabs', |
|||
templateUrl: './entity-view-tabs.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class EntityViewTabsComponent extends EntityTabsComponent<EntityViewInfo> { |
|||
|
|||
constructor(protected store: Store<AppState>) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
super.ngOnInit(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2019 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN" |
|||
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab"> |
|||
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table> |
|||
</mat-tab> |
|||
@ -0,0 +1,39 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component } from '@angular/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; |
|||
import { AssetInfo } from '@app/shared/models/asset.models'; |
|||
import { RuleChain } from '@shared/models/rule-chain.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-rulechain-tabs', |
|||
templateUrl: './rulechain-tabs.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class RuleChainTabsComponent extends EntityTabsComponent<RuleChain> { |
|||
|
|||
constructor(protected store: Store<AppState>) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
super.ngOnInit(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,102 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { BaseData } from './base-data'; |
|||
import { AuditLogId } from './id/audit-log-id'; |
|||
import { CustomerId } from './id/customer-id'; |
|||
import { EntityId } from './id/entity-id'; |
|||
import { UserId } from './id/user-id'; |
|||
import { TenantId } from './id/tenant-id'; |
|||
|
|||
export enum AuditLogMode { |
|||
TENANT, |
|||
ENTITY, |
|||
USER, |
|||
CUSTOMER |
|||
} |
|||
|
|||
export enum ActionType { |
|||
ADDED = 'ADDED', |
|||
DELETED = 'DELETED', |
|||
UPDATED = 'UPDATED', |
|||
ATTRIBUTES_UPDATED = 'ATTRIBUTES_UPDATED', |
|||
ATTRIBUTES_DELETED = 'ATTRIBUTES_DELETED', |
|||
RPC_CALL = 'RPC_CALL', |
|||
CREDENTIALS_UPDATED = 'CREDENTIALS_UPDATED', |
|||
ASSIGNED_TO_CUSTOMER = 'ASSIGNED_TO_CUSTOMER', |
|||
UNASSIGNED_FROM_CUSTOMER = 'UNASSIGNED_FROM_CUSTOMER', |
|||
ACTIVATED = 'ACTIVATED', |
|||
SUSPENDED = 'SUSPENDED', |
|||
CREDENTIALS_READ = 'CREDENTIALS_READ', |
|||
ATTRIBUTES_READ = 'ATTRIBUTES_READ', |
|||
RELATION_ADD_OR_UPDATE = 'RELATION_ADD_OR_UPDATE', |
|||
RELATION_DELETED = 'RELATION_DELETED', |
|||
RELATIONS_DELETED = 'RELATIONS_DELETED', |
|||
ALARM_ACK = 'ALARM_ACK', |
|||
ALARM_CLEAR = 'ALARM_CLEAR', |
|||
LOGIN = 'LOGIN', |
|||
LOGOUT = 'LOGOUT' |
|||
} |
|||
|
|||
export enum ActionStatus { |
|||
SUCCESS = 'SUCCESS', |
|||
FAILURE = 'FAILURE' |
|||
} |
|||
|
|||
export const actionTypeTranslations = new Map<ActionType, string>( |
|||
[ |
|||
[ActionType.ADDED, 'audit-log.type-added'], |
|||
[ActionType.DELETED, 'audit-log.type-deleted'], |
|||
[ActionType.UPDATED, 'audit-log.type-updated'], |
|||
[ActionType.ATTRIBUTES_UPDATED, 'audit-log.type-attributes-updated'], |
|||
[ActionType.ATTRIBUTES_DELETED, 'audit-log.type-attributes-deleted'], |
|||
[ActionType.RPC_CALL, 'audit-log.type-rpc-call'], |
|||
[ActionType.CREDENTIALS_UPDATED, 'audit-log.type-credentials-updated'], |
|||
[ActionType.ASSIGNED_TO_CUSTOMER, 'audit-log.type-assigned-to-customer'], |
|||
[ActionType.UNASSIGNED_FROM_CUSTOMER, 'audit-log.type-unassigned-from-customer'], |
|||
[ActionType.ACTIVATED, 'audit-log.type-activated'], |
|||
[ActionType.SUSPENDED, 'audit-log.type-suspended'], |
|||
[ActionType.CREDENTIALS_READ, 'audit-log.type-credentials-read'], |
|||
[ActionType.ATTRIBUTES_READ, 'audit-log.type-attributes-read'], |
|||
[ActionType.RELATION_ADD_OR_UPDATE, 'audit-log.type-relation-add-or-update'], |
|||
[ActionType.RELATION_DELETED, 'audit-log.type-relation-delete'], |
|||
[ActionType.RELATIONS_DELETED, 'audit-log.type-relations-delete'], |
|||
[ActionType.ALARM_ACK, 'audit-log.type-alarm-ack'], |
|||
[ActionType.ALARM_CLEAR, 'audit-log.type-alarm-clear'], |
|||
[ActionType.LOGIN, 'audit-log.type-login'], |
|||
[ActionType.LOGOUT, 'audit-log.type-logout'] |
|||
] |
|||
); |
|||
|
|||
export const actionStatusTranslations = new Map<ActionStatus, string>( |
|||
[ |
|||
[ActionStatus.SUCCESS, 'audit-log.status-success'], |
|||
[ActionStatus.FAILURE, 'audit-log.status-failure'], |
|||
] |
|||
); |
|||
|
|||
export interface AuditLog extends BaseData<AuditLogId> { |
|||
tenantId: TenantId; |
|||
customerId: CustomerId; |
|||
entityId: EntityId; |
|||
entityName: string; |
|||
userId: UserId; |
|||
userName: string; |
|||
actionType: ActionType; |
|||
actionData: any; |
|||
actionStatus: ActionStatus; |
|||
actionFailureDetails: string; |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { HasUUID } from '@shared/models/id/has-uuid'; |
|||
|
|||
export class AuditLogId implements HasUUID { |
|||
id: string; |
|||
constructor(id: string) { |
|||
this.id = id; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue