31 changed files with 1141 additions and 151 deletions
@ -0,0 +1,157 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sql; |
||||
|
|
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.hibernate.annotations.Type; |
||||
|
import org.hibernate.annotations.TypeDef; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.UUIDConverter; |
||||
|
import org.thingsboard.server.common.data.alarm.Alarm; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmId; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmStatus; |
||||
|
import org.thingsboard.server.common.data.id.EntityIdFactory; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.dao.model.BaseEntity; |
||||
|
import org.thingsboard.server.dao.model.BaseSqlEntity; |
||||
|
import org.thingsboard.server.dao.model.ModelConstants; |
||||
|
import org.thingsboard.server.dao.util.mapping.JsonStringType; |
||||
|
|
||||
|
import javax.persistence.*; |
||||
|
|
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY; |
||||
|
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY; |
||||
|
|
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@TypeDef(name = "json", typeClass = JsonStringType.class) |
||||
|
@MappedSuperclass |
||||
|
public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity<T> implements BaseEntity<T> { |
||||
|
|
||||
|
@Column(name = ALARM_TENANT_ID_PROPERTY) |
||||
|
private String tenantId; |
||||
|
|
||||
|
@Column(name = ALARM_ORIGINATOR_ID_PROPERTY) |
||||
|
private String originatorId; |
||||
|
|
||||
|
@Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY) |
||||
|
private EntityType originatorType; |
||||
|
|
||||
|
@Column(name = ALARM_TYPE_PROPERTY) |
||||
|
private String type; |
||||
|
|
||||
|
@Enumerated(EnumType.STRING) |
||||
|
@Column(name = ALARM_SEVERITY_PROPERTY) |
||||
|
private AlarmSeverity severity; |
||||
|
|
||||
|
@Enumerated(EnumType.STRING) |
||||
|
@Column(name = ALARM_STATUS_PROPERTY) |
||||
|
private AlarmStatus status; |
||||
|
|
||||
|
@Column(name = ALARM_START_TS_PROPERTY) |
||||
|
private Long startTs; |
||||
|
|
||||
|
@Column(name = ALARM_END_TS_PROPERTY) |
||||
|
private Long endTs; |
||||
|
|
||||
|
@Column(name = ALARM_ACK_TS_PROPERTY) |
||||
|
private Long ackTs; |
||||
|
|
||||
|
@Column(name = ALARM_CLEAR_TS_PROPERTY) |
||||
|
private Long clearTs; |
||||
|
|
||||
|
@Type(type = "json") |
||||
|
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) |
||||
|
private JsonNode details; |
||||
|
|
||||
|
@Column(name = ALARM_PROPAGATE_PROPERTY) |
||||
|
private Boolean propagate; |
||||
|
|
||||
|
public AbstractAlarmEntity() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public AbstractAlarmEntity(Alarm alarm) { |
||||
|
if (alarm.getId() != null) { |
||||
|
this.setId(alarm.getId().getId()); |
||||
|
} |
||||
|
if (alarm.getTenantId() != null) { |
||||
|
this.tenantId = UUIDConverter.fromTimeUUID(alarm.getTenantId().getId()); |
||||
|
} |
||||
|
this.type = alarm.getType(); |
||||
|
this.originatorId = UUIDConverter.fromTimeUUID(alarm.getOriginator().getId()); |
||||
|
this.originatorType = alarm.getOriginator().getEntityType(); |
||||
|
this.type = alarm.getType(); |
||||
|
this.severity = alarm.getSeverity(); |
||||
|
this.status = alarm.getStatus(); |
||||
|
this.propagate = alarm.isPropagate(); |
||||
|
this.startTs = alarm.getStartTs(); |
||||
|
this.endTs = alarm.getEndTs(); |
||||
|
this.ackTs = alarm.getAckTs(); |
||||
|
this.clearTs = alarm.getClearTs(); |
||||
|
this.details = alarm.getDetails(); |
||||
|
} |
||||
|
|
||||
|
public AbstractAlarmEntity(AlarmEntity alarmEntity) { |
||||
|
this.setId(alarmEntity.getId()); |
||||
|
this.tenantId = alarmEntity.getTenantId(); |
||||
|
this.type = alarmEntity.getType(); |
||||
|
this.originatorId = alarmEntity.getOriginatorId(); |
||||
|
this.originatorType = alarmEntity.getOriginatorType(); |
||||
|
this.type = alarmEntity.getType(); |
||||
|
this.severity = alarmEntity.getSeverity(); |
||||
|
this.status = alarmEntity.getStatus(); |
||||
|
this.propagate = alarmEntity.getPropagate(); |
||||
|
this.startTs = alarmEntity.getStartTs(); |
||||
|
this.endTs = alarmEntity.getEndTs(); |
||||
|
this.ackTs = alarmEntity.getAckTs(); |
||||
|
this.clearTs = alarmEntity.getClearTs(); |
||||
|
this.details = alarmEntity.getDetails(); |
||||
|
} |
||||
|
|
||||
|
protected Alarm toAlarm() { |
||||
|
Alarm alarm = new Alarm(new AlarmId(UUIDConverter.fromString(id))); |
||||
|
alarm.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id))); |
||||
|
if (tenantId != null) { |
||||
|
alarm.setTenantId(new TenantId(UUIDConverter.fromString(tenantId))); |
||||
|
} |
||||
|
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, UUIDConverter.fromString(originatorId))); |
||||
|
alarm.setType(type); |
||||
|
alarm.setSeverity(severity); |
||||
|
alarm.setStatus(status); |
||||
|
alarm.setPropagate(propagate); |
||||
|
alarm.setStartTs(startTs); |
||||
|
alarm.setEndTs(endTs); |
||||
|
alarm.setAckTs(ackTs); |
||||
|
alarm.setClearTs(clearTs); |
||||
|
alarm.setDetails(details); |
||||
|
return alarm; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sql; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmInfo; |
||||
|
|
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> { |
||||
|
|
||||
|
private String originatorName; |
||||
|
|
||||
|
public AlarmInfoEntity() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public AlarmInfoEntity(AlarmEntity alarmEntity) { |
||||
|
super(alarmEntity); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public AlarmInfo toData() { |
||||
|
return new AlarmInfo(super.toAlarm(), this.originatorName); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
///
|
||||
|
/// 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 { PageData } from '@shared/models/page/page-data'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
import { |
||||
|
Alarm, |
||||
|
AlarmInfo, |
||||
|
AlarmQuery, |
||||
|
AlarmSearchStatus, |
||||
|
AlarmSeverity, |
||||
|
AlarmStatus |
||||
|
} from '@shared/models/alarm.models'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class AlarmService { |
||||
|
|
||||
|
constructor( |
||||
|
private http: HttpClient |
||||
|
) { } |
||||
|
|
||||
|
public getAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> { |
||||
|
return this.http.get<Alarm>(`/api/alarm/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public getAlarmInfo(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmInfo> { |
||||
|
return this.http.get<AlarmInfo>(`/api/alarm/info/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public saveAlarm(alarm: Alarm, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> { |
||||
|
return this.http.post<Alarm>('/api/alarm', alarm, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public ackAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> { |
||||
|
return this.http.post<void>(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public clearAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> { |
||||
|
return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public getAlarms(query: AlarmQuery, |
||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AlarmInfo>> { |
||||
|
return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`, |
||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus, |
||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmSeverity> { |
||||
|
let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`; |
||||
|
if (alarmSearchStatus) { |
||||
|
url += `?searchStatus=${alarmSearchStatus}`; |
||||
|
} else if (alarmStatus) { |
||||
|
url += `?status=${alarmStatus}`; |
||||
|
} |
||||
|
return this.http.get<AlarmSeverity>(url, |
||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,123 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="alarmFormGroup" style="width: 600px;"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2>{{ 'alarm.alarm-details' | translate }}</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button mat-icon-button |
||||
|
(click)="close()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<div mat-dialog-content> |
||||
|
<fieldset [disabled]="isLoading$ | async"> |
||||
|
<div fxLayout="row" fxLayoutGap="6px"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>alarm.created-time</mat-label> |
||||
|
<input matInput formControlName="createdTime" readonly> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>alarm.originator</mat-label> |
||||
|
<input matInput formControlName="originatorName" readonly> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="6px" *ngIf="alarmFormGroup.get('startTime').value || |
||||
|
alarmFormGroup.get('endTime').value"> |
||||
|
<mat-form-field *ngIf="alarmFormGroup.get('startTime').value" fxFlex class="mat-block"> |
||||
|
<mat-label translate>alarm.start-time</mat-label> |
||||
|
<input matInput formControlName="startTime" readonly> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="alarmFormGroup.get('endTime').value" fxFlex class="mat-block"> |
||||
|
<mat-label translate>alarm.end-time</mat-label> |
||||
|
<input matInput formControlName="endTime" readonly> |
||||
|
</mat-form-field> |
||||
|
<span fxFlex *ngIf="!alarmFormGroup.get('startTime').value || !alarmFormGroup.get('endTime').value"></span> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="6px" *ngIf="alarmFormGroup.get('ackTime').value || |
||||
|
alarmFormGroup.get('clearTime').value"> |
||||
|
<mat-form-field *ngIf="alarmFormGroup.get('ackTime').value" fxFlex class="mat-block"> |
||||
|
<mat-label translate>alarm.ack-time</mat-label> |
||||
|
<input matInput formControlName="ackTime" readonly> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field *ngIf="alarmFormGroup.get('clearTime').value" fxFlex class="mat-block"> |
||||
|
<mat-label translate>alarm.clear-time</mat-label> |
||||
|
<input matInput formControlName="clearTime" readonly> |
||||
|
</mat-form-field> |
||||
|
<span fxFlex *ngIf="!alarmFormGroup.get('ackTime').value || !alarmFormGroup.get('clearTime').value"></span> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutGap="6px"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>alarm.type</mat-label> |
||||
|
<input matInput formControlName="type" readonly> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>alarm.severity</mat-label> |
||||
|
<input matInput formControlName="alarmSeverity" readonly |
||||
|
[ngStyle]="{fontWeight: 'bold', color: alarmSeverityColorsMap.get((alarm$ | async)?.severity)}"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>alarm.status</mat-label> |
||||
|
<input matInput formControlName="alarmStatus" readonly> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<tb-json-object-edit |
||||
|
*ngIf="displayDetails" |
||||
|
formControlName="alarmDetails" |
||||
|
readonly |
||||
|
label="{{ 'alarm.details' | translate }}"> |
||||
|
</tb-json-object-edit> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<div fxLayout="row" *ngIf="alarm$ | async; let alarm;"> |
||||
|
<button *ngIf="allowAcknowledgment && (alarm.status === alarmStatuses.ACTIVE_UNACK || |
||||
|
alarm.status === alarmStatuses.CLEARED_UNACK)" |
||||
|
mat-button |
||||
|
mat-raised-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="acknowledge()" |
||||
|
style="margin-right: 20px;" |
||||
|
[disabled]="(isLoading$ | async)"> |
||||
|
{{ 'alarm.acknowledge' | translate }} |
||||
|
</button> |
||||
|
<button *ngIf="allowClear && (alarm.status === alarmStatuses.ACTIVE_ACK || |
||||
|
alarm.status === alarmStatuses.ACTIVE_UNACK)" |
||||
|
mat-button |
||||
|
mat-raised-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="clear()" |
||||
|
[disabled]="(isLoading$ | async)"> |
||||
|
{{ 'alarm.clear' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button color="primary" |
||||
|
style="margin-right: 20px;" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="close()" cdkFocusInitial> |
||||
|
{{ 'action.close' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,155 @@ |
|||||
|
///
|
||||
|
/// 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, Inject, OnInit } from '@angular/core'; |
||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { FormBuilder, FormGroup } from '@angular/forms'; |
||||
|
import { Observable, Subject } from 'rxjs'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { DialogComponent } from '@app/shared/components/dialog.component'; |
||||
|
import { |
||||
|
AlarmInfo, |
||||
|
alarmSeverityColors, |
||||
|
alarmSeverityTranslations, |
||||
|
AlarmStatus, |
||||
|
alarmStatusTranslations |
||||
|
} from '@app/shared/models/alarm.models'; |
||||
|
import { AlarmService } from '@core/http/alarm.service'; |
||||
|
import { share, tap } from 'rxjs/operators'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
|
||||
|
export interface AlarmDetailsDialogData { |
||||
|
alarmId: string; |
||||
|
allowAcknowledgment: boolean; |
||||
|
allowClear: boolean; |
||||
|
displayDetails: boolean; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-alarm-details-dialog', |
||||
|
templateUrl: './alarm-details-dialog.component.html', |
||||
|
styleUrls: [] |
||||
|
}) |
||||
|
export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDialogComponent, boolean> implements OnInit { |
||||
|
|
||||
|
alarmFormGroup: FormGroup; |
||||
|
|
||||
|
allowAcknowledgment: boolean; |
||||
|
allowClear: boolean; |
||||
|
displayDetails: boolean; |
||||
|
|
||||
|
loadAlarmSubject = new Subject<AlarmInfo>(); |
||||
|
alarm$: Observable<AlarmInfo> = this.loadAlarmSubject.asObservable().pipe( |
||||
|
tap(alarm => this.loadAlarmFields(alarm)), |
||||
|
share() |
||||
|
); |
||||
|
|
||||
|
alarmSeverityColorsMap = alarmSeverityColors; |
||||
|
alarmStatuses = AlarmStatus; |
||||
|
|
||||
|
alarmUpdated = false; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
private datePipe: DatePipe, |
||||
|
private translate: TranslateService, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: AlarmDetailsDialogData, |
||||
|
private alarmService: AlarmService, |
||||
|
public dialogRef: MatDialogRef<AlarmDetailsDialogComponent, boolean>, |
||||
|
public fb: FormBuilder) { |
||||
|
super(store, router, dialogRef); |
||||
|
|
||||
|
this.allowAcknowledgment = data.allowAcknowledgment; |
||||
|
this.allowClear = data.allowClear; |
||||
|
this.displayDetails = data.displayDetails; |
||||
|
|
||||
|
this.alarmFormGroup = this.fb.group( |
||||
|
{ |
||||
|
createdTime: [''], |
||||
|
originatorName: [''], |
||||
|
startTime: [''], |
||||
|
endTime: [''], |
||||
|
ackTime: [''], |
||||
|
clearTime: [''], |
||||
|
type: [''], |
||||
|
alarmSeverity: [''], |
||||
|
alarmStatus: [''], |
||||
|
alarmDetails: [null] |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
this.loadAlarm(); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
loadAlarm() { |
||||
|
this.alarmService.getAlarmInfo(this.data.alarmId).subscribe( |
||||
|
alarm => this.loadAlarmSubject.next(alarm) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
loadAlarmFields(alarm: AlarmInfo) { |
||||
|
this.alarmFormGroup.get('createdTime') |
||||
|
.patchValue(this.datePipe.transform(alarm.createdTime, 'yyyy-MM-dd HH:mm:ss')); |
||||
|
this.alarmFormGroup.get('originatorName') |
||||
|
.patchValue(alarm.originatorName); |
||||
|
if (alarm.startTs) { |
||||
|
this.alarmFormGroup.get('startTime') |
||||
|
.patchValue(this.datePipe.transform(alarm.startTs, 'yyyy-MM-dd HH:mm:ss')); |
||||
|
} |
||||
|
if (alarm.endTs) { |
||||
|
this.alarmFormGroup.get('endTime') |
||||
|
.patchValue(this.datePipe.transform(alarm.endTs, 'yyyy-MM-dd HH:mm:ss')); |
||||
|
} |
||||
|
if (alarm.ackTs) { |
||||
|
this.alarmFormGroup.get('ackTime') |
||||
|
.patchValue(this.datePipe.transform(alarm.ackTs, 'yyyy-MM-dd HH:mm:ss')); |
||||
|
} |
||||
|
if (alarm.clearTs) { |
||||
|
this.alarmFormGroup.get('clearTime') |
||||
|
.patchValue(this.datePipe.transform(alarm.clearTs, 'yyyy-MM-dd HH:mm:ss')); |
||||
|
} |
||||
|
this.alarmFormGroup.get('type').patchValue(alarm.type); |
||||
|
this.alarmFormGroup.get('alarmSeverity') |
||||
|
.patchValue(this.translate.instant(alarmSeverityTranslations.get(alarm.severity))); |
||||
|
this.alarmFormGroup.get('alarmStatus') |
||||
|
.patchValue(this.translate.instant(alarmStatusTranslations.get(alarm.status))); |
||||
|
this.alarmFormGroup.get('alarmDetails').patchValue(alarm.details); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
close(): void { |
||||
|
this.dialogRef.close(this.alarmUpdated); |
||||
|
} |
||||
|
|
||||
|
acknowledge(): void { |
||||
|
this.alarmService.ackAlarm(this.data.alarmId).subscribe( |
||||
|
() => { this.alarmUpdated = true; this.loadAlarm(); } |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
clear(): void { |
||||
|
this.alarmService.clearAlarm(this.data.alarmId).subscribe( |
||||
|
() => { this.alarmUpdated = true; this.loadAlarm(); } |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,137 @@ |
|||||
|
///
|
||||
|
/// 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 { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models'; |
||||
|
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 { 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 { |
||||
|
AlarmInfo, |
||||
|
AlarmQuery, |
||||
|
AlarmSearchStatus, |
||||
|
alarmSeverityColors, |
||||
|
alarmSeverityTranslations, |
||||
|
alarmStatusTranslations |
||||
|
} from '@app/shared/models/alarm.models'; |
||||
|
import { AlarmService } from '@app/core/http/alarm.service'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; |
||||
|
import { |
||||
|
AuditLogDetailsDialogComponent, |
||||
|
AuditLogDetailsDialogData |
||||
|
} from '@home/components/audit-log/audit-log-details-dialog.component'; |
||||
|
import { |
||||
|
AlarmDetailsDialogComponent, |
||||
|
AlarmDetailsDialogData |
||||
|
} from '@home/components/alarm/alarm-details-dialog.component'; |
||||
|
|
||||
|
export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> { |
||||
|
|
||||
|
searchStatus: AlarmSearchStatus; |
||||
|
|
||||
|
constructor(private alarmService: AlarmService, |
||||
|
private dialogService: DialogService, |
||||
|
private translate: TranslateService, |
||||
|
private datePipe: DatePipe, |
||||
|
private dialog: MatDialog, |
||||
|
public entityId: EntityId = null, |
||||
|
private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY) { |
||||
|
super(); |
||||
|
this.loadDataOnInit = false; |
||||
|
this.tableTitle = ''; |
||||
|
this.useTimePageLink = true; |
||||
|
this.detailsPanelEnabled = false; |
||||
|
this.selectionEnabled = false; |
||||
|
this.searchEnabled = true; |
||||
|
this.addEnabled = false; |
||||
|
this.entitiesDeleteEnabled = false; |
||||
|
this.actionsColumnTitle = 'alarm.details'; |
||||
|
this.entityType = EntityType.ALARM; |
||||
|
this.entityTranslations = entityTypeTranslations.get(EntityType.ALARM); |
||||
|
this.entityResources = { |
||||
|
} as EntityTypeResource; |
||||
|
this.searchStatus = defaultSearchStatus; |
||||
|
|
||||
|
this.headerComponent = AlarmTableHeaderComponent; |
||||
|
|
||||
|
this.entitiesFetchFunction = pageLink => this.fetchAlarms(pageLink); |
||||
|
|
||||
|
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
||||
|
|
||||
|
this.columns.push( |
||||
|
new DateEntityTableColumn<AlarmInfo>('createdTime', 'alarm.created-time', this.datePipe, '150px')); |
||||
|
this.columns.push( |
||||
|
new EntityTableColumn<AlarmInfo>('originatorName', 'alarm.originator', '100%', |
||||
|
(entity) => entity.originatorName, entity => ({}), false)); |
||||
|
this.columns.push( |
||||
|
new EntityTableColumn<AlarmInfo>('type', 'alarm.type', '100%')); |
||||
|
this.columns.push( |
||||
|
new EntityTableColumn<AlarmInfo>('severity', 'alarm.severity', '100%', |
||||
|
(entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)), |
||||
|
entity => ({ |
||||
|
fontWeight: 'bold', |
||||
|
color: alarmSeverityColors.get(entity.severity) |
||||
|
}))); |
||||
|
this.columns.push( |
||||
|
new EntityTableColumn<AlarmInfo>('status', 'alarm.status', '100%', |
||||
|
(entity) => this.translate.instant(alarmStatusTranslations.get(entity.status)))); |
||||
|
|
||||
|
this.cellActionDescriptors.push( |
||||
|
{ |
||||
|
name: this.translate.instant('alarm.details'), |
||||
|
icon: 'more_horiz', |
||||
|
isEnabled: () => true, |
||||
|
onAction: ($event, entity) => this.showAlarmDetails(entity) |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> { |
||||
|
const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true); |
||||
|
return this.alarmService.getAlarms(query); |
||||
|
} |
||||
|
|
||||
|
showAlarmDetails(entity: AlarmInfo) { |
||||
|
this.dialog.open<AlarmDetailsDialogComponent, AlarmDetailsDialogData, boolean> |
||||
|
(AlarmDetailsDialogComponent, |
||||
|
{ |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
alarmId: entity.id.id, |
||||
|
allowAcknowledgment: true, |
||||
|
allowClear: true, |
||||
|
displayDetails: true |
||||
|
} |
||||
|
}).afterClosed().subscribe( |
||||
|
(res) => { |
||||
|
if (res) { |
||||
|
this.table.updateData(); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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-form-field class="mat-block" style="width: 200px;"> |
||||
|
<mat-label translate>alarm.alarm-status</mat-label> |
||||
|
<mat-select matInput [ngModel]="alarmTableConfig.searchStatus" |
||||
|
(ngModelChange)="searchStatusChanged($event)"> |
||||
|
<mat-option *ngFor="let status of alarmSearchStatusTypes" [value]="status"> |
||||
|
{{ alarmSearchStatusTranslationsMap.get(status) | translate }} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,35 @@ |
|||||
|
/** |
||||
|
* 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 { |
||||
|
/* flex: 1; |
||||
|
display: flex; |
||||
|
justify-content: flex-start; */ |
||||
|
padding-right: 8px; |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
mat-form-field { |
||||
|
font-size: 16px; |
||||
|
|
||||
|
.mat-form-field-wrapper { |
||||
|
padding-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.mat-form-field-underline { |
||||
|
bottom: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
///
|
||||
|
/// 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 { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component'; |
||||
|
import { AlarmInfo, AlarmSearchStatus, alarmSearchStatusTranslations } from '@shared/models/alarm.models'; |
||||
|
import { AlarmTableConfig } from './alarm-table-config'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-alarm-table-header', |
||||
|
templateUrl: './alarm-table-header.component.html', |
||||
|
styleUrls: ['./alarm-table-header.component.scss'] |
||||
|
}) |
||||
|
export class AlarmTableHeaderComponent extends EntityTableHeaderComponent<AlarmInfo> { |
||||
|
|
||||
|
alarmSearchStatusTranslationsMap = alarmSearchStatusTranslations; |
||||
|
|
||||
|
alarmSearchStatusTypes = Object.keys(AlarmSearchStatus); |
||||
|
|
||||
|
get alarmTableConfig(): AlarmTableConfig { |
||||
|
return this.entitiesTableConfig as AlarmTableConfig; |
||||
|
} |
||||
|
|
||||
|
constructor(protected store: Store<AppState>) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
searchStatusChanged(searchStatus: AlarmSearchStatus) { |
||||
|
this.alarmTableConfig.searchStatus = searchStatus; |
||||
|
this.alarmTableConfig.table.updateData(); |
||||
|
} |
||||
|
} |
||||
@ -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]="alarmTableConfig"></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 { |
||||
|
.mat-drawer-container { |
||||
|
background-color: white; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
///
|
||||
|
/// 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 { TranslateService } from '@ngx-translate/core'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
import { MatDialog } from '@angular/material'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
import { AlarmTableConfig } from './alarm-table-config'; |
||||
|
import { AlarmSearchStatus } from '@shared/models/alarm.models'; |
||||
|
import { AlarmService } from '@app/core/http/alarm.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-alarm-table', |
||||
|
templateUrl: './alarm-table.component.html', |
||||
|
styleUrls: ['./alarm-table.component.scss'] |
||||
|
}) |
||||
|
export class AlarmTableComponent implements OnInit { |
||||
|
|
||||
|
activeValue = false; |
||||
|
dirtyValue = false; |
||||
|
entityIdValue: EntityId; |
||||
|
|
||||
|
@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.alarmTableConfig && this.alarmTableConfig.entityId !== entityId) { |
||||
|
this.alarmTableConfig.searchStatus = AlarmSearchStatus.ANY; |
||||
|
this.alarmTableConfig.entityId = entityId; |
||||
|
this.entitiesTable.resetSortAndFilter(this.activeValue); |
||||
|
if (!this.activeValue) { |
||||
|
this.dirtyValue = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; |
||||
|
|
||||
|
alarmTableConfig: AlarmTableConfig; |
||||
|
|
||||
|
constructor(private alarmService: AlarmService, |
||||
|
private dialogService: DialogService, |
||||
|
private translate: TranslateService, |
||||
|
private datePipe: DatePipe, |
||||
|
private dialog: MatDialog, |
||||
|
private store: Store<AppState>) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.dirtyValue = !this.activeValue; |
||||
|
this.alarmTableConfig = new AlarmTableConfig( |
||||
|
this.alarmService, |
||||
|
this.dialogService, |
||||
|
this.translate, |
||||
|
this.datePipe, |
||||
|
this.dialog, |
||||
|
this.entityIdValue, |
||||
|
AlarmSearchStatus.ANY |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue