UI: Add bars widget. Improve tooltips and aggregation.

This commit is contained in:
Igor Kulikov 2017-02-27 15:07:43 +02:00
parent e4963de151
commit a0d7e4be05
16 changed files with 1044 additions and 317 deletions

View File

@ -32,6 +32,13 @@ import org.thingsboard.server.exception.ThingsboardException;
@RequestMapping("/api") @RequestMapping("/api")
public class DashboardController extends BaseController { 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')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody

View File

@ -28,6 +28,7 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionT
@Data @Data
public class TimeseriesSubscriptionCmd extends SubscriptionCmd { public class TimeseriesSubscriptionCmd extends SubscriptionCmd {
private long startTs;
private long timeWindow; private long timeWindow;
private int limit; private int limit;
private String agg; private String agg;

View File

@ -191,8 +191,8 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
if (cmd.getTimeWindow() > 0) { if (cmd.getTimeWindow() > 0) {
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); 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()); log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), cmd.getDeviceId());
long endTs = System.currentTimeMillis(); startTs = cmd.getStartTs();
startTs = endTs - cmd.getTimeWindow(); 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()); 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)); ctx.loadTimeseries(deviceId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys));
} else { } else {
@ -234,7 +234,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
return new PluginCallback<List<TsKvEntry>>() { return new PluginCallback<List<TsKvEntry>>() {
@Override @Override
public void onSuccess(PluginContext ctx, List<TsKvEntry> data) { 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()); Map<String, Long> subState = new HashMap<>(keys.size());
keys.forEach(key -> subState.put(key, startTs)); keys.forEach(key -> subState.put(key, startTs));

View File

@ -26,16 +26,10 @@ public class SubscriptionUpdate {
private int errorCode; private int errorCode;
private String errorMsg; private String errorMsg;
private Map<String, List<Object>> data; private Map<String, List<Object>> data;
private long serverStartTs;
public SubscriptionUpdate(int subscriptionId, List<TsKvEntry> data) { public SubscriptionUpdate(int subscriptionId, List<TsKvEntry> data) {
this(subscriptionId, 0L, data);
}
public SubscriptionUpdate(int subscriptionId, long serverStartTs, List<TsKvEntry> data) {
super(); super();
this.subscriptionId = subscriptionId; this.subscriptionId = subscriptionId;
this.serverStartTs = serverStartTs;
this.data = new TreeMap<>(); this.data = new TreeMap<>();
for (TsKvEntry tsEntry : data) { for (TsKvEntry tsEntry : data) {
List<Object> values = this.data.get(tsEntry.getKey()); List<Object> values = this.data.get(tsEntry.getKey());
@ -95,13 +89,9 @@ public class SubscriptionUpdate {
return errorMsg; return errorMsg;
} }
public long getServerStartTs() {
return serverStartTs;
}
@Override @Override
public String toString() { public String toString() {
return "SubscriptionUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", data=" return "SubscriptionUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", data="
+ data + ", serverStartTs=" + serverStartTs+ "]"; + data + "]";
} }
} }

View File

@ -22,6 +22,7 @@ function DashboardService($http, $q) {
var service = { var service = {
assignDashboardToCustomer: assignDashboardToCustomer, assignDashboardToCustomer: assignDashboardToCustomer,
getCustomerDashboards: getCustomerDashboards, getCustomerDashboards: getCustomerDashboards,
getServerTimeDiff: getServerTimeDiff,
getDashboard: getDashboard, getDashboard: getDashboard,
getTenantDashboards: getTenantDashboards, getTenantDashboards: getTenantDashboards,
deleteDashboard: deleteDashboard, deleteDashboard: deleteDashboard,
@ -71,6 +72,21 @@ function DashboardService($http, $q) {
return deferred.promise; 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) { function getDashboard(dashboardId) {
var deferred = $q.defer(); var deferred = $q.defer();
var url = '/api/dashboard/' + dashboardId; var url = '/api/dashboard/' + dashboardId;

View File

@ -16,31 +16,26 @@
export default class DataAggregator { 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.onDataCb = onDataCb;
this.tsKeyNames = tsKeyNames;
this.startTs = startTs;
this.aggregationType = aggregationType; this.aggregationType = aggregationType;
this.types = types; this.types = types;
this.$timeout = $timeout; this.$timeout = $timeout;
this.$filter = $filter; this.$filter = $filter;
this.dataReceived = false; this.dataReceived = false;
this.noAggregation = aggregationType === types.aggregation.none.value; this.noAggregation = aggregationType === types.aggregation.none.value;
var interval = Math.floor(timeWindow / limit); this.limit = limit;
if (!this.noAggregation) { this.timeWindow = timeWindow;
this.interval = Math.max(interval, 1000); this.interval = interval;
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.aggregationTimeout = this.interval; this.aggregationTimeout = this.interval;
switch (aggregationType) { switch (aggregationType) {
case types.aggregation.min.value: case types.aggregation.min.value:
this.aggFunction = min; this.aggFunction = min;
break; break;
case types.aggregation.max.value: case types.aggregation.max.value:
this.aggFunction = max this.aggFunction = max;
break; break;
case types.aggregation.avg.value: case types.aggregation.avg.value:
this.aggFunction = avg; this.aggFunction = avg;
@ -59,42 +54,56 @@ export default class DataAggregator {
} }
} }
onData(data) { onData(data, update, history) {
if (!this.dataReceived) { if (!this.dataReceived) {
this.elapsed = 0; this.elapsed = 0;
this.dataReceived = true; this.dataReceived = true;
this.startTs = data.serverStartTs;
this.endTs = this.startTs + this.timeWindow; this.endTs = this.startTs + this.timeWindow;
this.aggregationMap = processAggregatedData(data.data, this.aggregationType === this.types.aggregation.count.value, this.noAggregation); if (update) {
this.onInterval(currentTime()); 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(), history);
} else { } else {
updateAggregatedData(this.aggregationMap, this.aggregationType === this.types.aggregation.count.value, updateAggregatedData(this.aggregationMap, this.aggregationType === this.types.aggregation.count.value,
this.noAggregation, this.aggFunction, data.data, this.interval, this.startTs); this.noAggregation, this.aggFunction, data.data, this.interval, this.startTs);
if (history) {
this.onInterval(currentTime(), history);
}
} }
} }
onInterval(startedTime) { onInterval(startedTime, history) {
var now = currentTime(); var now = currentTime();
this.elapsed += now - startedTime; this.elapsed += now - startedTime;
if (this.intervalTimeoutHandle) { if (this.intervalTimeoutHandle) {
this.$timeout.cancel(this.intervalTimeoutHandle); this.$timeout.cancel(this.intervalTimeoutHandle);
this.intervalTimeoutHandle = null; this.intervalTimeoutHandle = null;
} }
var delta = Math.floor(this.elapsed / this.interval); if (!history) {
if (delta || !this.data) { var delta = Math.floor(this.elapsed / this.interval);
this.startTs += delta * this.interval; if (delta || !this.data) {
this.endTs += delta * this.interval; this.startTs += delta * this.interval;
this.data = toData(this.aggregationMap, this.startTs, this.endTs, this.$filter, this.limit); this.endTs += delta * this.interval;
this.elapsed = this.elapsed - delta * this.interval; 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) { if (this.onDataCb) {
this.onDataCb(this.data, this.startTs, this.endTs); this.onDataCb(this.data, this.startTs, this.endTs);
} }
var self = this; var self = this;
this.intervalTimeoutHandle = this.$timeout(function() { if (!history) {
self.onInterval(now); this.intervalTimeoutHandle = this.$timeout(function() {
}, this.aggregationTimeout, false); self.onInterval(now);
}, this.aggregationTimeout, false);
}
} }
reset() { reset() {
@ -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 = {}; var data = {};
for (var k in tsKeyNames) {
data[tsKeyNames[k]] = [];
}
for (var key in aggregationMap) { for (var key in aggregationMap) {
if (!data[key]) {
data[key] = [];
}
var aggKeyData = aggregationMap[key]; var aggKeyData = aggregationMap[key];
var keyData = data[key]; var keyData = data[key];
for (var aggTimestamp in aggKeyData) { for (var aggTimestamp in aggKeyData) {
@ -185,7 +194,7 @@ function toData(aggregationMap, startTs, endTs, $filter, limit) {
delete aggKeyData[aggTimestamp]; delete aggKeyData[aggTimestamp];
} else if (aggTimestamp <= endTs) { } else if (aggTimestamp <= endTs) {
var aggData = aggKeyData[aggTimestamp]; var aggData = aggKeyData[aggTimestamp];
var kvPair = [aggTimestamp, aggData.aggValue]; var kvPair = [Number(aggTimestamp), aggData.aggValue];
keyData.push(kvPair); keyData.push(kvPair);
} }
} }

View File

@ -108,9 +108,9 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
datasourceSubscription.subscriptionTimewindow.fixedWindow; datasourceSubscription.subscriptionTimewindow.fixedWindow;
var realtime = datasourceSubscription.subscriptionTimewindow && var realtime = datasourceSubscription.subscriptionTimewindow &&
datasourceSubscription.subscriptionTimewindow.realtimeWindowMs; datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
var dataGenFunction = null;
var timer; var timer;
var frequency; var frequency;
var dataAggregator;
var subscription = { var subscription = {
addListener: addListener, addListener: addListener,
@ -131,19 +131,20 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
dataKey.index = i; dataKey.index = i;
var key; var key;
if (datasourceType === types.datasourceType.function) { if (datasourceType === types.datasourceType.function) {
key = utils.objectHashCode(dataKey);
if (!dataKey.func) { if (!dataKey.func) {
dataKey.func = new Function("time", "prevValue", dataKey.funcBody); dataKey.func = new Function("time", "prevValue", dataKey.funcBody);
} }
datasourceData[key] = { } else {
data: []
};
dataKeys[key] = dataKey;
} else if (datasourceType === types.datasourceType.device) {
key = dataKey.name + '_' + dataKey.type;
if (dataKey.postFuncBody && !dataKey.postFunc) { if (dataKey.postFuncBody && !dataKey.postFunc) {
dataKey.postFunc = new Function("time", "value", "prevValue", dataKey.postFuncBody); 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]; var dataKeysList = dataKeys[key];
if (!dataKeysList) { if (!dataKeysList) {
dataKeysList = []; dataKeysList = [];
@ -153,24 +154,19 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
datasourceData[key + '_' + index] = { datasourceData[key + '_' + index] = {
data: [] data: []
}; };
} else {
key = utils.objectHashCode(dataKey);
datasourceData[key] = {
data: []
};
dataKeys[key] = dataKey;
} }
dataKey.key = key; dataKey.key = key;
} }
if (datasourceType === types.datasourceType.function) { if (datasourceType === types.datasourceType.function) {
frequency = 1000; frequency = 1000;
if (datasourceSubscription.type === types.widgetType.timeseries.value) { if (datasourceSubscription.type === types.widgetType.timeseries.value) {
dataGenFunction = generateSeries; frequency = Math.min(datasourceSubscription.subscriptionTimewindow.aggregation.interval, 5000);
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;
} }
} }
} }
@ -193,14 +189,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
function syncListener(listener) { function syncListener(listener) {
var key; var key;
var dataKey; var dataKey;
if (datasourceType === types.datasourceType.function) { if (datasourceType === types.datasourceType.device || datasourceSubscription.type === types.widgetType.timeseries.value) {
for (key in dataKeys) {
dataKey = dataKeys[key];
listener.dataUpdated(datasourceData[key],
listener.datasourceIndex,
dataKey.index);
}
} else if (datasourceType === types.datasourceType.device) {
for (key in dataKeys) { for (key in dataKeys) {
var dataKeysList = dataKeys[key]; var dataKeysList = dataKeys[key];
for (var i = 0; i < dataKeysList.length; i++) { for (var i = 0; i < dataKeysList.length; i++) {
@ -211,6 +200,13 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
dataKey.index); 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()) { if (history && !hasListeners()) {
return; return;
} }
//$log.debug("started!"); var subsTw = datasourceSubscription.subscriptionTimewindow;
var tsKeyNames = [];
var dataKey;
if (datasourceType === types.datasourceType.device) { if (datasourceType === types.datasourceType.device) {
//send subscribe command //send subscribe command
@ -228,12 +227,13 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
for (var key in dataKeys) { for (var key in dataKeys) {
var dataKeysList = dataKeys[key]; var dataKeysList = dataKeys[key];
var dataKey = dataKeysList[0]; dataKey = dataKeysList[0];
if (dataKey.type === types.dataKeyType.timeseries) { if (dataKey.type === types.dataKeyType.timeseries) {
if (tsKeys.length > 0) { if (tsKeys.length > 0) {
tsKeys += ','; tsKeys += ',';
} }
tsKeys += dataKey.name; tsKeys += dataKey.name;
tsKeyNames.push(dataKey.name);
} else if (dataKey.type === types.dataKeyType.attribute) { } else if (dataKey.type === types.dataKeyType.attribute) {
if (attrKeys.length > 0) { if (attrKeys.length > 0) {
attrKeys += ','; attrKeys += ',';
@ -252,10 +252,10 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
var historyCommand = { var historyCommand = {
deviceId: datasourceSubscription.deviceId, deviceId: datasourceSubscription.deviceId,
keys: tsKeys, keys: tsKeys,
startTs: datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs, startTs: subsTw.fixedWindow.startTimeMs,
endTs: datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs, endTs: subsTw.fixedWindow.endTimeMs,
limit: datasourceSubscription.subscriptionTimewindow.aggregation.limit, limit: subsTw.aggregation.limit,
agg: datasourceSubscription.subscriptionTimewindow.aggregation.type agg: subsTw.aggregation.type
}; };
subscriber = { subscriber = {
@ -287,16 +287,20 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
}; };
if (datasourceSubscription.type === types.widgetType.timeseries.value) { if (datasourceSubscription.type === types.widgetType.timeseries.value) {
subscriptionCommand.timeWindow = datasourceSubscription.subscriptionTimewindow.realtimeWindowMs; subscriptionCommand.startTs = subsTw.startTs;
subscriptionCommand.limit = datasourceSubscription.subscriptionTimewindow.aggregation.limit; subscriptionCommand.timeWindow = subsTw.aggregation.timeWindow;
subscriptionCommand.agg = datasourceSubscription.subscriptionTimewindow.aggregation.type; subscriptionCommand.limit = subsTw.aggregation.limit;
var dataAggregator = new DataAggregator( subscriptionCommand.agg = subsTw.aggregation.type;
dataAggregator = new DataAggregator(
function(data, startTs, endTs) { function(data, startTs, endTs) {
onData(data, types.dataKeyType.timeseries, startTs, endTs); onData(data, types.dataKeyType.timeseries, startTs, endTs);
}, },
subscriptionCommand.limit, tsKeyNames,
subscriptionCommand.agg, subsTw.startTs,
subscriptionCommand.timeWindow, subsTw.aggregation.limit,
subsTw.aggregation.type,
subsTw.aggregation.timeWindow,
subsTw.aggregation.interval,
types, types,
$timeout, $timeout,
$filter $filter
@ -308,9 +312,6 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
dataAggregator.reset(); dataAggregator.reset();
onReconnected(); onReconnected();
} }
subscriber.onDestroy = function() {
dataAggregator.destroy();
}
} else { } else {
subscriber.onReconnected = function() { subscriber.onReconnected = function() {
onReconnected(); 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) { if (history) {
onTick(); onTick();
} else { } else {
@ -377,30 +401,17 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
} }
subscribers = {}; subscribers = {};
} }
} if (dataAggregator) {
dataAggregator.destroy();
function boundToInterval(data, timewindowMs) { dataAggregator = null;
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 data = [];
var prevSeries; var prevSeries;
var datasourceKeyData = datasourceData[dataKey.key].data; var datasourceDataKey = dataKey.key + '_' + index;
var datasourceKeyData = datasourceData[datasourceDataKey].data;
if (datasourceKeyData.length > 0) { if (datasourceKeyData.length > 0) {
prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; prevSeries = datasourceKeyData[datasourceKeyData.length - 1];
} else { } else {
@ -417,18 +428,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
if (data.length > 0) { if (data.length > 0) {
dataKey.lastUpdateTime = data[data.length - 1][0]; dataKey.lastUpdateTime = data[data.length - 1][0];
} }
if (realtime) { return data;
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);
}
} }
function generateLatest(dataKey) { function generateLatest(dataKey) {
@ -458,23 +458,32 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
if (datasourceSubscription.type === types.widgetType.timeseries.value) { if (datasourceSubscription.type === types.widgetType.timeseries.value) {
var startTime; var startTime;
var endTime; var endTime;
for (key in dataKeys) { var generatedData = {
var dataKey = dataKeys[key]; data: {
if (!startTime) { }
if (realtime) { };
endTime = (new Date).getTime(); for (key in dataKeys) {
if (dataKey.lastUpdateTime) { var dataKeyList = dataKeys[key];
startTime = dataKey.lastUpdateTime + frequency; for (var index = 0; index < dataKeyList.length; index ++) {
} else { var dataKey = dataKeyList[index];
startTime = endTime - datasourceSubscription.subscriptionTimewindow.realtimeWindowMs; if (!startTime) {
} if (realtime) {
} else { if (dataKey.lastUpdateTime) {
startTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs; startTime = dataKey.lastUpdateTime + frequency
endTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs; } else {
} startTime = datasourceSubscription.subscriptionTimewindow.startTs;
}
endTime = startTime + datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
} else {
startTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
endTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs;
}
}
var data = generateSeries(dataKey, index, startTime, endTime);
generatedData.data[dataKey.name+'_'+dataKey.index] = data;
} }
generateSeries(dataKey, startTime, endTime);
} }
dataAggregator.onData(generatedData, true, history);
} else if (datasourceSubscription.type === types.widgetType.latest.value) { } else if (datasourceSubscription.type === types.widgetType.latest.value) {
for (key in dataKeys) { for (key in dataKeys) {
generateLatest(dataKeys[key]); generateLatest(dataKeys[key]);
@ -568,8 +577,6 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
} }
if (data.length > 0 || (startTs && endTs)) { if (data.length > 0 || (startTs && endTs)) {
datasourceData[datasourceKey].data = data; datasourceData[datasourceKey].data = data;
datasourceData[datasourceKey].startTs = startTs;
datasourceData[datasourceKey].endTs = endTs;
for (var i2 in listeners) { for (var i2 in listeners) {
var listener = listeners[i2]; var listener = listeners[i2];
listener.dataUpdated(datasourceData[datasourceKey], listener.dataUpdated(datasourceData[datasourceKey],

View File

@ -68,6 +68,7 @@ function Dashboard() {
prepareDashboardContextMenu: '&?', prepareDashboardContextMenu: '&?',
prepareWidgetContextMenu: '&?', prepareWidgetContextMenu: '&?',
loadWidgets: '&?', loadWidgets: '&?',
getStDiff: '&?',
onInit: '&?', onInit: '&?',
onInitFailed: '&?', onInitFailed: '&?',
dashboardStyle: '=?' dashboardStyle: '=?'
@ -94,6 +95,8 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
vm.gridster = null; vm.gridster = null;
vm.stDiff = 0;
vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false; vm.isMobileDisabled = angular.isDefined(vm.isMobileDisabled) ? vm.isMobileDisabled : false;
vm.dashboardLoading = true; vm.dashboardLoading = true;
@ -302,7 +305,28 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
}); });
}); });
loadDashboard(); 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() { function loadDashboard() {
resetWidgetClick(); resetWidgetClick();

View File

@ -93,7 +93,7 @@
</div> </div>
<div flex layout="column" class="tb-widget-content"> <div flex layout="column" class="tb-widget-content">
<div flex tb-widget <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> </div>
</div> </div>

View File

@ -20,7 +20,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
/*@ngInject*/ /*@ngInject*/
export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils, 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; var vm = this;
@ -46,7 +46,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
realtimeWindowMs: null, realtimeWindowMs: null,
aggregation: null aggregation: null
}; };
var dataUpdateTimer = null;
var dataUpdateCaf = null; var dataUpdateCaf = null;
/* /*
@ -72,7 +71,9 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
settings: widget.config.settings, settings: widget.config.settings,
datasources: widget.config.datasources, datasources: widget.config.datasources,
data: [], data: [],
timeWindow: {}, timeWindow: {
stDiff: stDiff
},
timewindowFunctions: { timewindowFunctions: {
onUpdateTimewindow: onUpdateTimewindow, onUpdateTimewindow: onUpdateTimewindow,
onResetTimewindow: onResetTimewindow 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) { if (subscriptionTimewindow.realtimeWindowMs) {
widgetContext.timeWindow.maxTime = endTs || (new Date).getTime(); widgetContext.timeWindow.maxTime = (new Date).getTime() + widgetContext.timeWindow.stDiff;
widgetContext.timeWindow.minTime = startTs || (widgetContext.timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs); widgetContext.timeWindow.minTime = widgetContext.timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs;
} else if (subscriptionTimewindow.fixedWindow) { } else if (subscriptionTimewindow.fixedWindow) {
widgetContext.timeWindow.maxTime = subscriptionTimewindow.fixedWindow.endTimeMs; widgetContext.timeWindow.maxTime = subscriptionTimewindow.fixedWindow.endTimeMs;
widgetContext.timeWindow.minTime = subscriptionTimewindow.fixedWindow.startTimeMs; widgetContext.timeWindow.minTime = subscriptionTimewindow.fixedWindow.startTimeMs;
@ -165,10 +167,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
} }
function onDataUpdated() { function onDataUpdated() {
if (dataUpdateTimer) {
$timeout.cancel(dataUpdateTimer);
dataUpdateTimer = null;
}
if (widgetContext.inited) { if (widgetContext.inited) {
if (dataUpdateCaf) { if (dataUpdateCaf) {
dataUpdateCaf(); dataUpdateCaf();
@ -496,7 +494,8 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
startTimeMs: startTimeMs, startTimeMs: startTimeMs,
endTimeMs: endTimeMs endTimeMs: endTimeMs
} }
} },
aggregation: angular.copy(widget.config.timewindow.aggregation)
}; };
} }
@ -513,14 +512,10 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
} }
if (update) { if (update) {
if (subscriptionTimewindow.realtimeWindowMs) { if (subscriptionTimewindow.realtimeWindowMs) {
updateTimewindow(sourceData.startTs, sourceData.endTs); updateTimewindow();
} }
widgetContext.data[datasourceIndex + dataKeyIndex].data = sourceData.data; widgetContext.data[datasourceIndex + dataKeyIndex].data = sourceData.data;
if (widgetContext.data.length > 1 && !dataUpdateTimer) { onDataUpdated();
dataUpdateTimer = $timeout(onDataUpdated, 300, false);
} else {
onDataUpdated();
}
} }
} }
@ -552,10 +547,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
function unsubscribe() { function unsubscribe() {
if (widget.type !== types.widgetType.rpc.value) { if (widget.type !== types.widgetType.rpc.value) {
if (dataUpdateTimer) {
$timeout.cancel(dataUpdateTimer);
dataUpdateTimer = null;
}
for (var i in datasourceListeners) { for (var i in datasourceListeners) {
var listener = datasourceListeners[i]; var listener = datasourceListeners[i];
datasourceService.unsubscribeFromDatasource(listener); datasourceService.unsubscribeFromDatasource(listener);
@ -575,7 +566,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
}; };
if (widget.type === types.widgetType.timeseries.value && if (widget.type === types.widgetType.timeseries.value &&
angular.isDefined(widget.config.timewindow)) { angular.isDefined(widget.config.timewindow)) {
var timeWindow = 0;
if (angular.isDefined(widget.config.timewindow.aggregation)) { if (angular.isDefined(widget.config.timewindow.aggregation)) {
subscriptionTimewindow.aggregation = { subscriptionTimewindow.aggregation = {
limit: widget.config.timewindow.aggregation.limit || 200, 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)) { if (angular.isDefined(widget.config.timewindow.realtime)) {
subscriptionTimewindow.realtimeWindowMs = widget.config.timewindow.realtime.timewindowMs; 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)) { } else if (angular.isDefined(widget.config.timewindow.history)) {
if (angular.isDefined(widget.config.timewindow.history.timewindowMs)) { if (angular.isDefined(widget.config.timewindow.history.timewindowMs)) {
var currentTime = (new Date).getTime(); 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, startTimeMs: currentTime - widget.config.timewindow.history.timewindowMs,
endTimeMs: currentTime endTimeMs: currentTime
} }
timeWindow = widget.config.timewindow.history.timewindowMs;
} else { } else {
subscriptionTimewindow.fixedWindow = { subscriptionTimewindow.fixedWindow = {
startTimeMs: widget.config.timewindow.history.fixedTimewindow.startTimeMs, startTimeMs: widget.config.timewindow.history.fixedTimewindow.startTimeMs,
endTimeMs: widget.config.timewindow.history.fixedTimewindow.endTimeMs 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(); updateTimewindow();
if (subscriptionTimewindow.fixedWindow) {
onDataUpdated();
}
} }
for (var i in widget.config.datasources) { for (var i in widget.config.datasources) {
var datasource = widget.config.datasources[i]; var datasource = widget.config.datasources[i];

View File

@ -61,6 +61,7 @@ export default function DashboardController(types, widgetService, userService,
vm.isTenantAdmin = isTenantAdmin; vm.isTenantAdmin = isTenantAdmin;
vm.isSystemAdmin = isSystemAdmin; vm.isSystemAdmin = isSystemAdmin;
vm.loadDashboard = loadDashboard; vm.loadDashboard = loadDashboard;
vm.getServerTimeDiff = getServerTimeDiff;
vm.noData = noData; vm.noData = noData;
vm.onAddWidgetClosed = onAddWidgetClosed; vm.onAddWidgetClosed = onAddWidgetClosed;
vm.onEditWidgetClosed = onEditWidgetClosed; vm.onEditWidgetClosed = onEditWidgetClosed;
@ -94,10 +95,9 @@ export default function DashboardController(types, widgetService, userService,
widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then( widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then(
function (widgetTypes) { function (widgetTypes) {
widgetTypes = $filter('orderBy')(widgetTypes, ['-name']); widgetTypes = $filter('orderBy')(widgetTypes, ['-createdTime']);
var top = 0; var top = 0;
var sizeY = 0;
if (widgetTypes.length > 0) { if (widgetTypes.length > 0) {
loadNext(0); loadNext(0);
@ -135,7 +135,7 @@ export default function DashboardController(types, widgetService, userService,
} else if (widgetTypeInfo.type === types.widgetType.static.value) { } else if (widgetTypeInfo.type === types.widgetType.static.value) {
vm.staticWidgetTypes.push(widget); vm.staticWidgetTypes.push(widget);
} }
top += sizeY; top += widget.sizeY;
loadNextOrComplete(i); loadNextOrComplete(i);
} }
@ -144,6 +144,10 @@ export default function DashboardController(types, widgetService, userService,
} }
} }
function getServerTimeDiff() {
return dashboardService.getServerTimeDiff();
}
function loadDashboard() { function loadDashboard() {
var deferred = $q.defer(); var deferred = $q.defer();

View File

@ -91,6 +91,7 @@
prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)" prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)"
on-remove-widget="vm.removeWidget(event, widget)" on-remove-widget="vm.removeWidget(event, widget)"
load-widgets="vm.loadDashboard()" load-widgets="vm.loadDashboard()"
get-st-diff="vm.getServerTimeDiff()"
on-init="vm.dashboardInited(dashboard)" on-init="vm.dashboardInited(dashboard)"
on-init-failed="vm.dashboardInitFailed(e)"> on-init-failed="vm.dashboardInitFailed(e)">
</tb-dashboard> </tb-dashboard>

View File

@ -29,7 +29,7 @@ import EditAttributeValueController from './edit-attribute-value.controller';
/*@ngInject*/ /*@ngInject*/
export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog, 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) { var linker = function (scope, element, attrs) {
@ -357,6 +357,10 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
scope.getDeviceAttributes(true); scope.getDeviceAttributes(true);
} }
scope.getServerTimeDiff = function() {
return dashboardService.getServerTimeDiff();
}
scope.addWidgetToDashboard = function($event) { scope.addWidgetToDashboard = function($event) {
if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) { if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) {
var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0]; var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0];

View File

@ -158,8 +158,9 @@
<tb-dashboard <tb-dashboard
device-alias-list="deviceAliases" device-alias-list="deviceAliases"
widgets="widgets" widgets="widgets"
get-st-diff="getServerTimeDiff()"
columns="20" columns="20"
is-edit="true" is-edit="false"
is-mobile-disabled="true" is-mobile-disabled="true"
is-edit-action-enabled="false" is-edit-action-enabled="false"
is-remove-action-enabled="false"> is-remove-action-enabled="false">

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,7 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then( widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then(
function (widgetTypes) { function (widgetTypes) {
widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','name']); widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','-createdTime']);
var top = 0; var top = 0;
var lastTop = [0, 0, 0]; var lastTop = [0, 0, 0];