UI: Add bars widget. Improve tooltips and aggregation.
This commit is contained in:
parent
e4963de151
commit
a0d7e4be05
@ -32,6 +32,13 @@ import org.thingsboard.server.exception.ThingsboardException;
|
||||
@RequestMapping("/api")
|
||||
public class DashboardController extends BaseController {
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public long getServerTime() throws ThingsboardException {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
|
||||
@ -28,6 +28,7 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionT
|
||||
@Data
|
||||
public class TimeseriesSubscriptionCmd extends SubscriptionCmd {
|
||||
|
||||
private long startTs;
|
||||
private long timeWindow;
|
||||
private int limit;
|
||||
private String agg;
|
||||
|
||||
@ -191,8 +191,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
|
||||
if (cmd.getTimeWindow() > 0) {
|
||||
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
|
||||
log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), cmd.getDeviceId());
|
||||
long endTs = System.currentTimeMillis();
|
||||
startTs = endTs - cmd.getTimeWindow();
|
||||
startTs = cmd.getStartTs();
|
||||
long endTs = cmd.getStartTs() + cmd.getTimeWindow();
|
||||
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
|
||||
ctx.loadTimeseries(deviceId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys));
|
||||
} else {
|
||||
@ -234,7 +234,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
|
||||
return new PluginCallback<List<TsKvEntry>>() {
|
||||
@Override
|
||||
public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
|
||||
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), startTs, data));
|
||||
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
|
||||
|
||||
Map<String, Long> subState = new HashMap<>(keys.size());
|
||||
keys.forEach(key -> subState.put(key, startTs));
|
||||
|
||||
@ -26,16 +26,10 @@ public class SubscriptionUpdate {
|
||||
private int errorCode;
|
||||
private String errorMsg;
|
||||
private Map<String, List<Object>> data;
|
||||
private long serverStartTs;
|
||||
|
||||
public SubscriptionUpdate(int subscriptionId, List<TsKvEntry> data) {
|
||||
this(subscriptionId, 0L, data);
|
||||
}
|
||||
|
||||
public SubscriptionUpdate(int subscriptionId, long serverStartTs, List<TsKvEntry> data) {
|
||||
super();
|
||||
this.subscriptionId = subscriptionId;
|
||||
this.serverStartTs = serverStartTs;
|
||||
this.data = new TreeMap<>();
|
||||
for (TsKvEntry tsEntry : data) {
|
||||
List<Object> values = this.data.get(tsEntry.getKey());
|
||||
@ -95,13 +89,9 @@ public class SubscriptionUpdate {
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
public long getServerStartTs() {
|
||||
return serverStartTs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SubscriptionUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", data="
|
||||
+ data + ", serverStartTs=" + serverStartTs+ "]";
|
||||
+ data + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ function DashboardService($http, $q) {
|
||||
var service = {
|
||||
assignDashboardToCustomer: assignDashboardToCustomer,
|
||||
getCustomerDashboards: getCustomerDashboards,
|
||||
getServerTimeDiff: getServerTimeDiff,
|
||||
getDashboard: getDashboard,
|
||||
getTenantDashboards: getTenantDashboards,
|
||||
deleteDashboard: deleteDashboard,
|
||||
@ -71,6 +72,21 @@ function DashboardService($http, $q) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getServerTimeDiff() {
|
||||
var deferred = $q.defer();
|
||||
var url = '/api/dashboard/serverTime';
|
||||
var ct1 = Date.now();
|
||||
$http.get(url, null).then(function success(response) {
|
||||
var ct2 = Date.now();
|
||||
var st = response.data;
|
||||
var stDiff = Math.ceil(st - (ct1+ct2)/2);
|
||||
deferred.resolve(stDiff);
|
||||
}, function fail() {
|
||||
deferred.reject();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getDashboard(dashboardId) {
|
||||
var deferred = $q.defer();
|
||||
var url = '/api/dashboard/' + dashboardId;
|
||||
|
||||
@ -16,31 +16,26 @@
|
||||
|
||||
export default class DataAggregator {
|
||||
|
||||
constructor(onDataCb, limit, aggregationType, timeWindow, types, $timeout, $filter) {
|
||||
constructor(onDataCb, tsKeyNames, startTs, limit, aggregationType, timeWindow, interval, types, $timeout, $filter) {
|
||||
this.onDataCb = onDataCb;
|
||||
this.tsKeyNames = tsKeyNames;
|
||||
this.startTs = startTs;
|
||||
this.aggregationType = aggregationType;
|
||||
this.types = types;
|
||||
this.$timeout = $timeout;
|
||||
this.$filter = $filter;
|
||||
this.dataReceived = false;
|
||||
this.noAggregation = aggregationType === types.aggregation.none.value;
|
||||
var interval = Math.floor(timeWindow / limit);
|
||||
if (!this.noAggregation) {
|
||||
this.interval = Math.max(interval, 1000);
|
||||
this.limit = Math.ceil(interval/this.interval * limit);
|
||||
this.timeWindow = this.interval * this.limit;
|
||||
} else {
|
||||
this.limit = limit;
|
||||
this.timeWindow = interval * this.limit;
|
||||
this.interval = 1000;
|
||||
}
|
||||
this.timeWindow = timeWindow;
|
||||
this.interval = interval;
|
||||
this.aggregationTimeout = this.interval;
|
||||
switch (aggregationType) {
|
||||
case types.aggregation.min.value:
|
||||
this.aggFunction = min;
|
||||
break;
|
||||
case types.aggregation.max.value:
|
||||
this.aggFunction = max
|
||||
this.aggFunction = max;
|
||||
break;
|
||||
case types.aggregation.avg.value:
|
||||
this.aggFunction = avg;
|
||||
@ -59,43 +54,57 @@ export default class DataAggregator {
|
||||
}
|
||||
}
|
||||
|
||||
onData(data) {
|
||||
onData(data, update, history) {
|
||||
if (!this.dataReceived) {
|
||||
this.elapsed = 0;
|
||||
this.dataReceived = true;
|
||||
this.startTs = data.serverStartTs;
|
||||
this.endTs = this.startTs + this.timeWindow;
|
||||
if (update) {
|
||||
this.aggregationMap = {};
|
||||
updateAggregatedData(this.aggregationMap, this.aggregationType === this.types.aggregation.count.value,
|
||||
this.noAggregation, this.aggFunction, data.data, this.interval, this.startTs);
|
||||
} else {
|
||||
this.aggregationMap = processAggregatedData(data.data, this.aggregationType === this.types.aggregation.count.value, this.noAggregation);
|
||||
this.onInterval(currentTime());
|
||||
}
|
||||
this.onInterval(currentTime(), history);
|
||||
} else {
|
||||
updateAggregatedData(this.aggregationMap, this.aggregationType === this.types.aggregation.count.value,
|
||||
this.noAggregation, this.aggFunction, data.data, this.interval, this.startTs);
|
||||
if (history) {
|
||||
this.onInterval(currentTime(), history);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onInterval(startedTime) {
|
||||
onInterval(startedTime, history) {
|
||||
var now = currentTime();
|
||||
this.elapsed += now - startedTime;
|
||||
if (this.intervalTimeoutHandle) {
|
||||
this.$timeout.cancel(this.intervalTimeoutHandle);
|
||||
this.intervalTimeoutHandle = null;
|
||||
}
|
||||
if (!history) {
|
||||
var delta = Math.floor(this.elapsed / this.interval);
|
||||
if (delta || !this.data) {
|
||||
this.startTs += delta * this.interval;
|
||||
this.endTs += delta * this.interval;
|
||||
this.data = toData(this.aggregationMap, this.startTs, this.endTs, this.$filter, this.limit);
|
||||
this.data = toData(this.tsKeyNames, this.aggregationMap, this.startTs, this.endTs, this.$filter, this.limit);
|
||||
this.elapsed = this.elapsed - delta * this.interval;
|
||||
}
|
||||
} else {
|
||||
this.data = toData(this.tsKeyNames, this.aggregationMap, this.startTs, this.endTs, this.$filter, this.limit);
|
||||
}
|
||||
if (this.onDataCb) {
|
||||
this.onDataCb(this.data, this.startTs, this.endTs);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
if (!history) {
|
||||
this.intervalTimeoutHandle = this.$timeout(function() {
|
||||
self.onInterval(now);
|
||||
}, this.aggregationTimeout, false);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.destroy();
|
||||
@ -172,12 +181,12 @@ function updateAggregatedData(aggregationMap, isCount, noAggregation, aggFunctio
|
||||
}
|
||||
}
|
||||
|
||||
function toData(aggregationMap, startTs, endTs, $filter, limit) {
|
||||
function toData(tsKeyNames, aggregationMap, startTs, endTs, $filter, limit) {
|
||||
var data = {};
|
||||
for (var key in aggregationMap) {
|
||||
if (!data[key]) {
|
||||
data[key] = [];
|
||||
for (var k in tsKeyNames) {
|
||||
data[tsKeyNames[k]] = [];
|
||||
}
|
||||
for (var key in aggregationMap) {
|
||||
var aggKeyData = aggregationMap[key];
|
||||
var keyData = data[key];
|
||||
for (var aggTimestamp in aggKeyData) {
|
||||
@ -185,7 +194,7 @@ function toData(aggregationMap, startTs, endTs, $filter, limit) {
|
||||
delete aggKeyData[aggTimestamp];
|
||||
} else if (aggTimestamp <= endTs) {
|
||||
var aggData = aggKeyData[aggTimestamp];
|
||||
var kvPair = [aggTimestamp, aggData.aggValue];
|
||||
var kvPair = [Number(aggTimestamp), aggData.aggValue];
|
||||
keyData.push(kvPair);
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,9 +108,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
datasourceSubscription.subscriptionTimewindow.fixedWindow;
|
||||
var realtime = datasourceSubscription.subscriptionTimewindow &&
|
||||
datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
|
||||
var dataGenFunction = null;
|
||||
var timer;
|
||||
var frequency;
|
||||
var dataAggregator;
|
||||
|
||||
var subscription = {
|
||||
addListener: addListener,
|
||||
@ -131,19 +131,20 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
dataKey.index = i;
|
||||
var key;
|
||||
if (datasourceType === types.datasourceType.function) {
|
||||
key = utils.objectHashCode(dataKey);
|
||||
if (!dataKey.func) {
|
||||
dataKey.func = new Function("time", "prevValue", dataKey.funcBody);
|
||||
}
|
||||
datasourceData[key] = {
|
||||
data: []
|
||||
};
|
||||
dataKeys[key] = dataKey;
|
||||
} else if (datasourceType === types.datasourceType.device) {
|
||||
key = dataKey.name + '_' + dataKey.type;
|
||||
} else {
|
||||
if (dataKey.postFuncBody && !dataKey.postFunc) {
|
||||
dataKey.postFunc = new Function("time", "value", "prevValue", dataKey.postFuncBody);
|
||||
}
|
||||
}
|
||||
if (datasourceType === types.datasourceType.device || datasourceSubscription.type === types.widgetType.timeseries.value) {
|
||||
if (datasourceType === types.datasourceType.function) {
|
||||
key = dataKey.name + '_' + dataKey.index + '_' + dataKey.type;
|
||||
} else {
|
||||
key = dataKey.name + '_' + dataKey.type;
|
||||
}
|
||||
var dataKeysList = dataKeys[key];
|
||||
if (!dataKeysList) {
|
||||
dataKeysList = [];
|
||||
@ -153,24 +154,19 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
datasourceData[key + '_' + index] = {
|
||||
data: []
|
||||
};
|
||||
} else {
|
||||
key = utils.objectHashCode(dataKey);
|
||||
datasourceData[key] = {
|
||||
data: []
|
||||
};
|
||||
dataKeys[key] = dataKey;
|
||||
}
|
||||
dataKey.key = key;
|
||||
}
|
||||
if (datasourceType === types.datasourceType.function) {
|
||||
frequency = 1000;
|
||||
if (datasourceSubscription.type === types.widgetType.timeseries.value) {
|
||||
dataGenFunction = generateSeries;
|
||||
var window;
|
||||
if (realtime) {
|
||||
window = datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
|
||||
} else {
|
||||
window = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs -
|
||||
datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
|
||||
}
|
||||
frequency = window / 1000 * 20;
|
||||
} else if (datasourceSubscription.type === types.widgetType.latest.value) {
|
||||
dataGenFunction = generateLatest;
|
||||
frequency = 1000;
|
||||
frequency = Math.min(datasourceSubscription.subscriptionTimewindow.aggregation.interval, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,14 +189,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
function syncListener(listener) {
|
||||
var key;
|
||||
var dataKey;
|
||||
if (datasourceType === types.datasourceType.function) {
|
||||
for (key in dataKeys) {
|
||||
dataKey = dataKeys[key];
|
||||
listener.dataUpdated(datasourceData[key],
|
||||
listener.datasourceIndex,
|
||||
dataKey.index);
|
||||
}
|
||||
} else if (datasourceType === types.datasourceType.device) {
|
||||
if (datasourceType === types.datasourceType.device || datasourceSubscription.type === types.widgetType.timeseries.value) {
|
||||
for (key in dataKeys) {
|
||||
var dataKeysList = dataKeys[key];
|
||||
for (var i = 0; i < dataKeysList.length; i++) {
|
||||
@ -211,6 +200,13 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
dataKey.index);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (key in dataKeys) {
|
||||
dataKey = dataKeys[key];
|
||||
listener.dataUpdated(datasourceData[key],
|
||||
listener.datasourceIndex,
|
||||
dataKey.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +214,10 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
if (history && !hasListeners()) {
|
||||
return;
|
||||
}
|
||||
//$log.debug("started!");
|
||||
var subsTw = datasourceSubscription.subscriptionTimewindow;
|
||||
var tsKeyNames = [];
|
||||
var dataKey;
|
||||
|
||||
if (datasourceType === types.datasourceType.device) {
|
||||
|
||||
//send subscribe command
|
||||
@ -228,12 +227,13 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
|
||||
for (var key in dataKeys) {
|
||||
var dataKeysList = dataKeys[key];
|
||||
var dataKey = dataKeysList[0];
|
||||
dataKey = dataKeysList[0];
|
||||
if (dataKey.type === types.dataKeyType.timeseries) {
|
||||
if (tsKeys.length > 0) {
|
||||
tsKeys += ',';
|
||||
}
|
||||
tsKeys += dataKey.name;
|
||||
tsKeyNames.push(dataKey.name);
|
||||
} else if (dataKey.type === types.dataKeyType.attribute) {
|
||||
if (attrKeys.length > 0) {
|
||||
attrKeys += ',';
|
||||
@ -252,10 +252,10 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
var historyCommand = {
|
||||
deviceId: datasourceSubscription.deviceId,
|
||||
keys: tsKeys,
|
||||
startTs: datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs,
|
||||
endTs: datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs,
|
||||
limit: datasourceSubscription.subscriptionTimewindow.aggregation.limit,
|
||||
agg: datasourceSubscription.subscriptionTimewindow.aggregation.type
|
||||
startTs: subsTw.fixedWindow.startTimeMs,
|
||||
endTs: subsTw.fixedWindow.endTimeMs,
|
||||
limit: subsTw.aggregation.limit,
|
||||
agg: subsTw.aggregation.type
|
||||
};
|
||||
|
||||
subscriber = {
|
||||
@ -287,16 +287,20 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
};
|
||||
|
||||
if (datasourceSubscription.type === types.widgetType.timeseries.value) {
|
||||
subscriptionCommand.timeWindow = datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
|
||||
subscriptionCommand.limit = datasourceSubscription.subscriptionTimewindow.aggregation.limit;
|
||||
subscriptionCommand.agg = datasourceSubscription.subscriptionTimewindow.aggregation.type;
|
||||
var dataAggregator = new DataAggregator(
|
||||
subscriptionCommand.startTs = subsTw.startTs;
|
||||
subscriptionCommand.timeWindow = subsTw.aggregation.timeWindow;
|
||||
subscriptionCommand.limit = subsTw.aggregation.limit;
|
||||
subscriptionCommand.agg = subsTw.aggregation.type;
|
||||
dataAggregator = new DataAggregator(
|
||||
function(data, startTs, endTs) {
|
||||
onData(data, types.dataKeyType.timeseries, startTs, endTs);
|
||||
},
|
||||
subscriptionCommand.limit,
|
||||
subscriptionCommand.agg,
|
||||
subscriptionCommand.timeWindow,
|
||||
tsKeyNames,
|
||||
subsTw.startTs,
|
||||
subsTw.aggregation.limit,
|
||||
subsTw.aggregation.type,
|
||||
subsTw.aggregation.timeWindow,
|
||||
subsTw.aggregation.interval,
|
||||
types,
|
||||
$timeout,
|
||||
$filter
|
||||
@ -308,9 +312,6 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
dataAggregator.reset();
|
||||
onReconnected();
|
||||
}
|
||||
subscriber.onDestroy = function() {
|
||||
dataAggregator.destroy();
|
||||
}
|
||||
} else {
|
||||
subscriber.onReconnected = function() {
|
||||
onReconnected();
|
||||
@ -353,7 +354,30 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
|
||||
}
|
||||
|
||||
} else if (dataGenFunction) {
|
||||
} else if (datasourceType === types.datasourceType.function) {
|
||||
if (datasourceSubscription.type === types.widgetType.timeseries.value) {
|
||||
for (key in dataKeys) {
|
||||
var dataKeyList = dataKeys[key];
|
||||
for (var index = 0; index < dataKeyList.length; index++) {
|
||||
dataKey = dataKeyList[index];
|
||||
tsKeyNames.push(dataKey.name+'_'+dataKey.index);
|
||||
}
|
||||
}
|
||||
dataAggregator = new DataAggregator(
|
||||
function (data, startTs, endTs) {
|
||||
onData(data, types.dataKeyType.function, startTs, endTs);
|
||||
},
|
||||
tsKeyNames,
|
||||
subsTw.startTs,
|
||||
subsTw.aggregation.limit,
|
||||
subsTw.aggregation.type,
|
||||
subsTw.aggregation.timeWindow,
|
||||
subsTw.aggregation.interval,
|
||||
types,
|
||||
$timeout,
|
||||
$filter
|
||||
);
|
||||
}
|
||||
if (history) {
|
||||
onTick();
|
||||
} else {
|
||||
@ -377,30 +401,17 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
}
|
||||
subscribers = {};
|
||||
}
|
||||
if (dataAggregator) {
|
||||
dataAggregator.destroy();
|
||||
dataAggregator = null;
|
||||
}
|
||||
}
|
||||
|
||||
function boundToInterval(data, timewindowMs) {
|
||||
if (data.length > 1) {
|
||||
var start = data[0][0];
|
||||
var end = data[data.length - 1][0];
|
||||
var i = 0;
|
||||
var currentInterval = end - start;
|
||||
while (currentInterval > timewindowMs && i < data.length - 2) {
|
||||
i++;
|
||||
start = data[i][0];
|
||||
currentInterval = end - start;
|
||||
}
|
||||
if (i > 1) {
|
||||
data.splice(0, i - 1);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function generateSeries(dataKey, startTime, endTime) {
|
||||
function generateSeries(dataKey, index, startTime, endTime) {
|
||||
var data = [];
|
||||
var prevSeries;
|
||||
var datasourceKeyData = datasourceData[dataKey.key].data;
|
||||
var datasourceDataKey = dataKey.key + '_' + index;
|
||||
var datasourceKeyData = datasourceData[datasourceDataKey].data;
|
||||
if (datasourceKeyData.length > 0) {
|
||||
prevSeries = datasourceKeyData[datasourceKeyData.length - 1];
|
||||
} else {
|
||||
@ -417,18 +428,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
if (data.length > 0) {
|
||||
dataKey.lastUpdateTime = data[data.length - 1][0];
|
||||
}
|
||||
if (realtime) {
|
||||
datasourceData[dataKey.key].data = boundToInterval(datasourceKeyData.concat(data),
|
||||
datasourceSubscription.subscriptionTimewindow.realtimeWindowMs);
|
||||
} else {
|
||||
datasourceData[dataKey.key].data = data;
|
||||
}
|
||||
for (var i in listeners) {
|
||||
var listener = listeners[i];
|
||||
listener.dataUpdated(datasourceData[dataKey.key],
|
||||
listener.datasourceIndex,
|
||||
dataKey.index);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function generateLatest(dataKey) {
|
||||
@ -458,23 +458,32 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
if (datasourceSubscription.type === types.widgetType.timeseries.value) {
|
||||
var startTime;
|
||||
var endTime;
|
||||
var generatedData = {
|
||||
data: {
|
||||
}
|
||||
};
|
||||
for (key in dataKeys) {
|
||||
var dataKey = dataKeys[key];
|
||||
var dataKeyList = dataKeys[key];
|
||||
for (var index = 0; index < dataKeyList.length; index ++) {
|
||||
var dataKey = dataKeyList[index];
|
||||
if (!startTime) {
|
||||
if (realtime) {
|
||||
endTime = (new Date).getTime();
|
||||
if (dataKey.lastUpdateTime) {
|
||||
startTime = dataKey.lastUpdateTime + frequency;
|
||||
startTime = dataKey.lastUpdateTime + frequency
|
||||
} else {
|
||||
startTime = endTime - datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
|
||||
startTime = datasourceSubscription.subscriptionTimewindow.startTs;
|
||||
}
|
||||
endTime = startTime + datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
|
||||
} else {
|
||||
startTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
|
||||
endTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs;
|
||||
}
|
||||
}
|
||||
generateSeries(dataKey, startTime, endTime);
|
||||
var data = generateSeries(dataKey, index, startTime, endTime);
|
||||
generatedData.data[dataKey.name+'_'+dataKey.index] = data;
|
||||
}
|
||||
}
|
||||
dataAggregator.onData(generatedData, true, history);
|
||||
} else if (datasourceSubscription.type === types.widgetType.latest.value) {
|
||||
for (key in dataKeys) {
|
||||
generateLatest(dataKeys[key]);
|
||||
@ -568,8 +577,6 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
|
||||
}
|
||||
if (data.length > 0 || (startTs && endTs)) {
|
||||
datasourceData[datasourceKey].data = data;
|
||||
datasourceData[datasourceKey].startTs = startTs;
|
||||
datasourceData[datasourceKey].endTs = endTs;
|
||||
for (var i2 in listeners) {
|
||||
var listener = listeners[i2];
|
||||
listener.dataUpdated(datasourceData[datasourceKey],
|
||||
|
||||
@ -68,6 +68,7 @@ function Dashboard() {
|
||||
prepareDashboardContextMenu: '&?',
|
||||
prepareWidgetContextMenu: '&?',
|
||||
loadWidgets: '&?',
|
||||
getStDiff: '&?',
|
||||
onInit: '&?',
|
||||
onInitFailed: '&?',
|
||||
dashboardStyle: '=?'
|
||||
@ -94,6 +95,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
|
||||
|
||||
vm.gridster = null;
|
||||
|
||||
vm.stDiff = 0;
|
||||
|
||||
vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false;
|
||||
|
||||
vm.dashboardLoading = true;
|
||||
@ -302,7 +305,28 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
|
||||
});
|
||||
});
|
||||
|
||||
loadStDiff();
|
||||
|
||||
function loadStDiff() {
|
||||
if (vm.getStDiff) {
|
||||
var promise = vm.getStDiff();
|
||||
if (promise) {
|
||||
promise.then(function (stDiff) {
|
||||
vm.stDiff = stDiff;
|
||||
loadDashboard();
|
||||
}, function () {
|
||||
vm.stDiff = 0;
|
||||
loadDashboard();
|
||||
});
|
||||
} else {
|
||||
vm.stDiff = 0;
|
||||
loadDashboard();
|
||||
}
|
||||
} else {
|
||||
vm.stDiff = 0;
|
||||
loadDashboard();
|
||||
}
|
||||
}
|
||||
|
||||
function loadDashboard() {
|
||||
resetWidgetClick();
|
||||
|
||||
@ -93,7 +93,7 @@
|
||||
</div>
|
||||
<div flex layout="column" class="tb-widget-content">
|
||||
<div flex tb-widget
|
||||
locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isEdit: vm.isEdit }">
|
||||
locals="{ visibleRect: vm.visibleRect, widget: widget, deviceAliasList: vm.deviceAliasList, isEdit: vm.isEdit, stDiff: vm.stDiff }">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
|
||||
|
||||
/*@ngInject*/
|
||||
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils,
|
||||
datasourceService, deviceService, visibleRect, isEdit, widget, deviceAliasList, widgetType) {
|
||||
datasourceService, deviceService, visibleRect, isEdit, stDiff, widget, deviceAliasList, widgetType) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
@ -46,7 +46,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
realtimeWindowMs: null,
|
||||
aggregation: null
|
||||
};
|
||||
var dataUpdateTimer = null;
|
||||
var dataUpdateCaf = null;
|
||||
|
||||
/*
|
||||
@ -72,7 +71,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
settings: widget.config.settings,
|
||||
datasources: widget.config.datasources,
|
||||
data: [],
|
||||
timeWindow: {},
|
||||
timeWindow: {
|
||||
stDiff: stDiff
|
||||
},
|
||||
timewindowFunctions: {
|
||||
onUpdateTimewindow: onUpdateTimewindow,
|
||||
onResetTimewindow: onResetTimewindow
|
||||
@ -154,10 +155,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
}
|
||||
}
|
||||
|
||||
function updateTimewindow(startTs, endTs) {
|
||||
function updateTimewindow() {
|
||||
widgetContext.timeWindow.interval = subscriptionTimewindow.aggregation.interval || 1000;
|
||||
if (subscriptionTimewindow.realtimeWindowMs) {
|
||||
widgetContext.timeWindow.maxTime = endTs || (new Date).getTime();
|
||||
widgetContext.timeWindow.minTime = startTs || (widgetContext.timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs);
|
||||
widgetContext.timeWindow.maxTime = (new Date).getTime() + widgetContext.timeWindow.stDiff;
|
||||
widgetContext.timeWindow.minTime = widgetContext.timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs;
|
||||
} else if (subscriptionTimewindow.fixedWindow) {
|
||||
widgetContext.timeWindow.maxTime = subscriptionTimewindow.fixedWindow.endTimeMs;
|
||||
widgetContext.timeWindow.minTime = subscriptionTimewindow.fixedWindow.startTimeMs;
|
||||
@ -165,10 +167,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
}
|
||||
|
||||
function onDataUpdated() {
|
||||
if (dataUpdateTimer) {
|
||||
$timeout.cancel(dataUpdateTimer);
|
||||
dataUpdateTimer = null;
|
||||
}
|
||||
if (widgetContext.inited) {
|
||||
if (dataUpdateCaf) {
|
||||
dataUpdateCaf();
|
||||
@ -496,7 +494,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
startTimeMs: startTimeMs,
|
||||
endTimeMs: endTimeMs
|
||||
}
|
||||
}
|
||||
},
|
||||
aggregation: angular.copy(widget.config.timewindow.aggregation)
|
||||
};
|
||||
}
|
||||
|
||||
@ -513,16 +512,12 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
}
|
||||
if (update) {
|
||||
if (subscriptionTimewindow.realtimeWindowMs) {
|
||||
updateTimewindow(sourceData.startTs, sourceData.endTs);
|
||||
updateTimewindow();
|
||||
}
|
||||
widgetContext.data[datasourceIndex + dataKeyIndex].data = sourceData.data;
|
||||
if (widgetContext.data.length > 1 && !dataUpdateTimer) {
|
||||
dataUpdateTimer = $timeout(onDataUpdated, 300, false);
|
||||
} else {
|
||||
onDataUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkSubscriptions() {
|
||||
if (widget.type !== types.widgetType.rpc.value) {
|
||||
@ -552,10 +547,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
|
||||
function unsubscribe() {
|
||||
if (widget.type !== types.widgetType.rpc.value) {
|
||||
if (dataUpdateTimer) {
|
||||
$timeout.cancel(dataUpdateTimer);
|
||||
dataUpdateTimer = null;
|
||||
}
|
||||
for (var i in datasourceListeners) {
|
||||
var listener = datasourceListeners[i];
|
||||
datasourceService.unsubscribeFromDatasource(listener);
|
||||
@ -575,7 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
};
|
||||
if (widget.type === types.widgetType.timeseries.value &&
|
||||
angular.isDefined(widget.config.timewindow)) {
|
||||
|
||||
var timeWindow = 0;
|
||||
if (angular.isDefined(widget.config.timewindow.aggregation)) {
|
||||
subscriptionTimewindow.aggregation = {
|
||||
limit: widget.config.timewindow.aggregation.limit || 200,
|
||||
@ -585,6 +576,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
|
||||
if (angular.isDefined(widget.config.timewindow.realtime)) {
|
||||
subscriptionTimewindow.realtimeWindowMs = widget.config.timewindow.realtime.timewindowMs;
|
||||
subscriptionTimewindow.startTs = (new Date).getTime() + widgetContext.timeWindow.stDiff - subscriptionTimewindow.realtimeWindowMs;
|
||||
timeWindow = subscriptionTimewindow.realtimeWindowMs;
|
||||
} else if (angular.isDefined(widget.config.timewindow.history)) {
|
||||
if (angular.isDefined(widget.config.timewindow.history.timewindowMs)) {
|
||||
var currentTime = (new Date).getTime();
|
||||
@ -592,14 +585,31 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
|
||||
startTimeMs: currentTime - widget.config.timewindow.history.timewindowMs,
|
||||
endTimeMs: currentTime
|
||||
}
|
||||
timeWindow = widget.config.timewindow.history.timewindowMs;
|
||||
} else {
|
||||
subscriptionTimewindow.fixedWindow = {
|
||||
startTimeMs: widget.config.timewindow.history.fixedTimewindow.startTimeMs,
|
||||
endTimeMs: widget.config.timewindow.history.fixedTimewindow.endTimeMs
|
||||
}
|
||||
timeWindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
|
||||
}
|
||||
subscriptionTimewindow.startTs = subscriptionTimewindow.fixedWindow.startTimeMs;
|
||||
}
|
||||
var aggregation = subscriptionTimewindow.aggregation;
|
||||
var noAggregation = aggregation.type === types.aggregation.none.value;
|
||||
var interval = Math.floor(timeWindow / aggregation.limit);
|
||||
if (!noAggregation) {
|
||||
aggregation.interval = Math.max(interval, 1000);
|
||||
aggregation.limit = Math.ceil(interval/aggregation.interval * aggregation.limit);
|
||||
aggregation.timeWindow = aggregation.interval * aggregation.limit;
|
||||
} else {
|
||||
aggregation.timeWindow = interval * aggregation.limit;
|
||||
aggregation.interval = 1000;
|
||||
}
|
||||
updateTimewindow();
|
||||
if (subscriptionTimewindow.fixedWindow) {
|
||||
onDataUpdated();
|
||||
}
|
||||
}
|
||||
for (var i in widget.config.datasources) {
|
||||
var datasource = widget.config.datasources[i];
|
||||
|
||||
@ -61,6 +61,7 @@ export default function DashboardController(types, widgetService, userService,
|
||||
vm.isTenantAdmin = isTenantAdmin;
|
||||
vm.isSystemAdmin = isSystemAdmin;
|
||||
vm.loadDashboard = loadDashboard;
|
||||
vm.getServerTimeDiff = getServerTimeDiff;
|
||||
vm.noData = noData;
|
||||
vm.onAddWidgetClosed = onAddWidgetClosed;
|
||||
vm.onEditWidgetClosed = onEditWidgetClosed;
|
||||
@ -94,10 +95,9 @@ export default function DashboardController(types, widgetService, userService,
|
||||
widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then(
|
||||
function (widgetTypes) {
|
||||
|
||||
widgetTypes = $filter('orderBy')(widgetTypes, ['-name']);
|
||||
widgetTypes = $filter('orderBy')(widgetTypes, ['-createdTime']);
|
||||
|
||||
var top = 0;
|
||||
var sizeY = 0;
|
||||
|
||||
if (widgetTypes.length > 0) {
|
||||
loadNext(0);
|
||||
@ -135,7 +135,7 @@ export default function DashboardController(types, widgetService, userService,
|
||||
} else if (widgetTypeInfo.type === types.widgetType.static.value) {
|
||||
vm.staticWidgetTypes.push(widget);
|
||||
}
|
||||
top += sizeY;
|
||||
top += widget.sizeY;
|
||||
loadNextOrComplete(i);
|
||||
|
||||
}
|
||||
@ -144,6 +144,10 @@ export default function DashboardController(types, widgetService, userService,
|
||||
}
|
||||
}
|
||||
|
||||
function getServerTimeDiff() {
|
||||
return dashboardService.getServerTimeDiff();
|
||||
}
|
||||
|
||||
function loadDashboard() {
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
@ -91,6 +91,7 @@
|
||||
prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
|
||||
on-remove-widget="vm.removeWidget(event, widget)"
|
||||
load-widgets="vm.loadDashboard()"
|
||||
get-st-diff="vm.getServerTimeDiff()"
|
||||
on-init="vm.dashboardInited(dashboard)"
|
||||
on-init-failed="vm.dashboardInitFailed(e)">
|
||||
</tb-dashboard>
|
||||
|
||||
@ -29,7 +29,7 @@ import EditAttributeValueController from './edit-attribute-value.controller';
|
||||
|
||||
/*@ngInject*/
|
||||
export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
|
||||
$document, $translate, utils, types, deviceService, widgetService) {
|
||||
$document, $translate, utils, types, dashboardService, deviceService, widgetService) {
|
||||
|
||||
var linker = function (scope, element, attrs) {
|
||||
|
||||
@ -357,6 +357,10 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
|
||||
scope.getDeviceAttributes(true);
|
||||
}
|
||||
|
||||
scope.getServerTimeDiff = function() {
|
||||
return dashboardService.getServerTimeDiff();
|
||||
}
|
||||
|
||||
scope.addWidgetToDashboard = function($event) {
|
||||
if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) {
|
||||
var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0];
|
||||
|
||||
@ -158,8 +158,9 @@
|
||||
<tb-dashboard
|
||||
device-alias-list="deviceAliases"
|
||||
widgets="widgets"
|
||||
get-st-diff="getServerTimeDiff()"
|
||||
columns="20"
|
||||
is-edit="true"
|
||||
is-edit="false"
|
||||
is-mobile-disabled="true"
|
||||
is-edit-action-enabled="false"
|
||||
is-remove-action-enabled="false">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -54,7 +54,7 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
|
||||
widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then(
|
||||
function (widgetTypes) {
|
||||
|
||||
widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','name']);
|
||||
widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','-createdTime']);
|
||||
|
||||
var top = 0;
|
||||
var lastTop = [0, 0, 0];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user