2019-08-29 12:08:49 +03:00
|
|
|
///
|
|
|
|
|
/// 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';
|
2020-01-22 20:05:30 +02:00
|
|
|
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
|
|
|
|
|
import { Observable } from 'rxjs';
|
2019-08-29 12:08:49 +03:00
|
|
|
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,
|
2020-01-22 20:05:30 +02:00
|
|
|
AlarmStatus,
|
|
|
|
|
simulatedAlarm
|
2019-08-29 12:08:49 +03:00
|
|
|
} from '@shared/models/alarm.models';
|
2019-09-10 15:12:10 +03:00
|
|
|
import { EntityType } from '@shared/models/entity-type.models';
|
2020-01-22 20:05:30 +02:00
|
|
|
import { Datasource, DatasourceType } from '@shared/models/widget.models';
|
|
|
|
|
import { SubscriptionTimewindow } from '@shared/models/time/time.models';
|
|
|
|
|
import { UtilsService } from '@core/services/utils.service';
|
|
|
|
|
import { TimePageLink } from '@shared/models/page/page-link';
|
|
|
|
|
import { Direction, SortOrder } from '@shared/models/page/sort-order';
|
|
|
|
|
import { concatMap, expand, map, toArray } from 'rxjs/operators';
|
|
|
|
|
import { EMPTY } from 'rxjs';
|
|
|
|
|
import Timeout = NodeJS.Timeout;
|
|
|
|
|
|
|
|
|
|
interface AlarmSourceListenerQuery {
|
|
|
|
|
entityType: EntityType;
|
|
|
|
|
entityId: string;
|
|
|
|
|
alarmSearchStatus: AlarmSearchStatus;
|
|
|
|
|
alarmStatus: AlarmStatus;
|
|
|
|
|
fetchOriginator?: boolean;
|
|
|
|
|
limit?: number;
|
|
|
|
|
interval?: number;
|
|
|
|
|
startTime?: number;
|
|
|
|
|
endTime?: number;
|
|
|
|
|
onAlarms?: (alarms: Array<AlarmInfo>) => void;
|
|
|
|
|
}
|
2019-09-10 15:12:10 +03:00
|
|
|
|
|
|
|
|
export interface AlarmSourceListener {
|
|
|
|
|
id?: string;
|
2020-01-22 20:05:30 +02:00
|
|
|
subscriptionTimewindow: SubscriptionTimewindow;
|
2019-09-10 15:12:10 +03:00
|
|
|
alarmSource: Datasource;
|
|
|
|
|
alarmsPollingInterval: number;
|
|
|
|
|
alarmSearchStatus: AlarmSearchStatus;
|
2020-01-22 20:05:30 +02:00
|
|
|
alarmsUpdated: (alarms: Array<AlarmInfo>) => void;
|
|
|
|
|
lastUpdateTs?: number;
|
|
|
|
|
alarmsQuery?: AlarmSourceListenerQuery;
|
|
|
|
|
pollTimer?: Timeout;
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
2019-08-29 12:08:49 +03:00
|
|
|
|
|
|
|
|
@Injectable({
|
|
|
|
|
providedIn: 'root'
|
|
|
|
|
})
|
|
|
|
|
export class AlarmService {
|
|
|
|
|
|
2020-01-22 20:05:30 +02:00
|
|
|
private alarmSourceListeners: {[id: string]: AlarmSourceListener} = {};
|
|
|
|
|
|
2019-08-29 12:08:49 +03:00
|
|
|
constructor(
|
2020-01-22 20:05:30 +02:00
|
|
|
private http: HttpClient,
|
|
|
|
|
private utils: UtilsService
|
2019-08-29 12:08:49 +03:00
|
|
|
) { }
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
public getAlarm(alarmId: string, config?: RequestConfig): Observable<Alarm> {
|
|
|
|
|
return this.http.get<Alarm>(`/api/alarm/${alarmId}`, defaultHttpOptionsFromConfig(config));
|
2019-08-29 12:08:49 +03:00
|
|
|
}
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
public getAlarmInfo(alarmId: string, config?: RequestConfig): Observable<AlarmInfo> {
|
|
|
|
|
return this.http.get<AlarmInfo>(`/api/alarm/info/${alarmId}`, defaultHttpOptionsFromConfig(config));
|
2019-08-29 12:08:49 +03:00
|
|
|
}
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
public saveAlarm(alarm: Alarm, config?: RequestConfig): Observable<Alarm> {
|
|
|
|
|
return this.http.post<Alarm>('/api/alarm', alarm, defaultHttpOptionsFromConfig(config));
|
2019-08-29 12:08:49 +03:00
|
|
|
}
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
public ackAlarm(alarmId: string, config?: RequestConfig): Observable<void> {
|
|
|
|
|
return this.http.post<void>(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptionsFromConfig(config));
|
2019-08-29 12:08:49 +03:00
|
|
|
}
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
public clearAlarm(alarmId: string, config?: RequestConfig): Observable<void> {
|
|
|
|
|
return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptionsFromConfig(config));
|
2019-08-29 12:08:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getAlarms(query: AlarmQuery,
|
2019-11-15 12:22:14 +02:00
|
|
|
config?: RequestConfig): Observable<PageData<AlarmInfo>> {
|
2019-08-29 12:08:49 +03:00
|
|
|
return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`,
|
2019-11-15 12:22:14 +02:00
|
|
|
defaultHttpOptionsFromConfig(config));
|
2019-08-29 12:08:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus,
|
2019-11-15 12:22:14 +02:00
|
|
|
config?: RequestConfig): Observable<AlarmSeverity> {
|
2019-08-29 12:08:49 +03:00
|
|
|
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,
|
2019-11-15 12:22:14 +02:00
|
|
|
defaultHttpOptionsFromConfig(config));
|
2019-08-29 12:08:49 +03:00
|
|
|
}
|
2020-01-22 20:05:30 +02:00
|
|
|
|
|
|
|
|
public subscribeForAlarms(alarmSourceListener: AlarmSourceListener): void {
|
|
|
|
|
alarmSourceListener.id = this.utils.guid();
|
|
|
|
|
this.alarmSourceListeners[alarmSourceListener.id] = alarmSourceListener;
|
|
|
|
|
const alarmSource = alarmSourceListener.alarmSource;
|
|
|
|
|
if (alarmSource.type === DatasourceType.function) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
alarmSourceListener.alarmsUpdated([simulatedAlarm]);
|
|
|
|
|
}, 0);
|
|
|
|
|
} else if (alarmSource.entityType && alarmSource.entityId) {
|
|
|
|
|
const pollingInterval = alarmSourceListener.alarmsPollingInterval;
|
|
|
|
|
alarmSourceListener.alarmsQuery = {
|
|
|
|
|
entityType: alarmSource.entityType,
|
|
|
|
|
entityId: alarmSource.entityId,
|
|
|
|
|
alarmSearchStatus: alarmSourceListener.alarmSearchStatus,
|
|
|
|
|
alarmStatus: null
|
|
|
|
|
};
|
|
|
|
|
const originatorKeys = alarmSource.dataKeys.filter(dataKey => dataKey.name.toLocaleLowerCase().includes('originator'));
|
|
|
|
|
if (originatorKeys.length) {
|
|
|
|
|
alarmSourceListener.alarmsQuery.fetchOriginator = true;
|
|
|
|
|
}
|
|
|
|
|
const subscriptionTimewindow = alarmSourceListener.subscriptionTimewindow;
|
|
|
|
|
if (subscriptionTimewindow.realtimeWindowMs) {
|
|
|
|
|
alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.startTs;
|
|
|
|
|
} else {
|
|
|
|
|
alarmSourceListener.alarmsQuery.startTime = subscriptionTimewindow.fixedWindow.startTimeMs;
|
|
|
|
|
alarmSourceListener.alarmsQuery.endTime = subscriptionTimewindow.fixedWindow.endTimeMs;
|
|
|
|
|
}
|
|
|
|
|
alarmSourceListener.alarmsQuery.onAlarms = (alarms) => {
|
|
|
|
|
if (subscriptionTimewindow.realtimeWindowMs) {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
if (alarmSourceListener.lastUpdateTs) {
|
|
|
|
|
const interval = now - alarmSourceListener.lastUpdateTs;
|
|
|
|
|
alarmSourceListener.alarmsQuery.startTime += interval;
|
|
|
|
|
}
|
|
|
|
|
alarmSourceListener.lastUpdateTs = now;
|
|
|
|
|
}
|
|
|
|
|
alarmSourceListener.alarmsUpdated(alarms);
|
|
|
|
|
};
|
|
|
|
|
this.onPollAlarms(alarmSourceListener.alarmsQuery);
|
|
|
|
|
alarmSourceListener.pollTimer = setInterval(this.onPollAlarms.bind(this), pollingInterval, alarmSourceListener.alarmsQuery);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public unsubscribeFromAlarms(alarmSourceListener: AlarmSourceListener): void {
|
|
|
|
|
if (alarmSourceListener && alarmSourceListener.id) {
|
|
|
|
|
if (alarmSourceListener.pollTimer) {
|
|
|
|
|
clearInterval(alarmSourceListener.pollTimer);
|
|
|
|
|
alarmSourceListener.pollTimer = null;
|
|
|
|
|
}
|
|
|
|
|
delete this.alarmSourceListeners[alarmSourceListener.id];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onPollAlarms(alarmsQuery: AlarmSourceListenerQuery): void {
|
|
|
|
|
this.getAlarmsByAlarmSourceQuery(alarmsQuery).subscribe((alarms) => {
|
|
|
|
|
alarmsQuery.onAlarms(alarms);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getAlarmsByAlarmSourceQuery(alarmsQuery: AlarmSourceListenerQuery): Observable<Array<AlarmInfo>> {
|
|
|
|
|
const time = Date.now();
|
|
|
|
|
let pageLink: TimePageLink;
|
|
|
|
|
const sortOrder: SortOrder = {property: 'createdTime', direction: Direction.DESC};
|
|
|
|
|
if (alarmsQuery.limit) {
|
|
|
|
|
pageLink = new TimePageLink(alarmsQuery.limit, 0,
|
|
|
|
|
null,
|
|
|
|
|
sortOrder);
|
|
|
|
|
} else if (alarmsQuery.interval) {
|
|
|
|
|
pageLink = new TimePageLink(100, 0,
|
|
|
|
|
null,
|
|
|
|
|
sortOrder, time - alarmsQuery.interval);
|
|
|
|
|
} else if (alarmsQuery.startTime) {
|
|
|
|
|
pageLink = new TimePageLink(100, 0,
|
|
|
|
|
null,
|
|
|
|
|
sortOrder, Math.round(alarmsQuery.startTime));
|
|
|
|
|
if (alarmsQuery.endTime) {
|
|
|
|
|
pageLink.endTime = Math.round(alarmsQuery.endTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return this.fetchAlarms(alarmsQuery, pageLink);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fetchAlarms(query: AlarmSourceListenerQuery,
|
|
|
|
|
pageLink: TimePageLink): Observable<Array<AlarmInfo>> {
|
|
|
|
|
const alarmQuery = new AlarmQuery(
|
|
|
|
|
{id: query.entityId, entityType: query.entityType},
|
|
|
|
|
pageLink,
|
|
|
|
|
query.alarmSearchStatus,
|
|
|
|
|
query.alarmStatus,
|
|
|
|
|
query.fetchOriginator);
|
|
|
|
|
return this.getAlarms(alarmQuery, {ignoreLoading: true}).pipe(
|
|
|
|
|
expand((data) => {
|
|
|
|
|
if (data.hasNext && !query.limit) {
|
|
|
|
|
alarmQuery.pageLink.page += 1;
|
|
|
|
|
return this.getAlarms(alarmQuery, {ignoreLoading: true});
|
|
|
|
|
} else {
|
|
|
|
|
return EMPTY;
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
map((data) => data.data),
|
|
|
|
|
concatMap((data) => data),
|
|
|
|
|
toArray(),
|
|
|
|
|
map((data) => data.sort((a, b) => alarmQuery.pageLink.sort(a, b))),
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-08-29 12:08:49 +03:00
|
|
|
}
|