From 7ca173de3de046501d79164da0c8c8871a03089b Mon Sep 17 00:00:00 2001
From: Trygve Laugstøl
Date: Sat, 5 Mar 2016 16:47:32 +0100
Subject: web: o Adding an API method to get per-hour aggregate values. Doesn't
use the by_hour table yet. o Adding a simple line graph component that can
graph a single property's value.
---
web/static/app/DillerRpc.js | 40 +++++-
web/static/app/app.js | 179 -------------------------
web/static/app/diller/client.js | 72 ++++++++++
web/static/app/diller/global.js | 60 +++++++++
web/static/app/diller/line-chart.js | 62 +++++++++
web/static/app/diller/web.js | 236 +++++++++++++++++++++++++++++++++
web/static/app/templates/property.html | 30 +++--
web/templates/index.jade | 9 +-
web/templates/wat.html | 2 +-
9 files changed, 491 insertions(+), 199 deletions(-)
delete mode 100644 web/static/app/app.js
create mode 100644 web/static/app/diller/client.js
create mode 100644 web/static/app/diller/global.js
create mode 100644 web/static/app/diller/line-chart.js
create mode 100644 web/static/app/diller/web.js
(limited to 'web')
diff --git a/web/static/app/DillerRpc.js b/web/static/app/DillerRpc.js
index f865dc5..eddbbf9 100644
--- a/web/static/app/DillerRpc.js
+++ b/web/static/app/DillerRpc.js
@@ -1,52 +1,82 @@
function DillerRpc($http, DillerConfig) {
var baseUrl = DillerConfig.baseUrl;
- function getDevices() {
+ function getDevicesReq() {
var req = {};
req.method = 'get';
req.url = baseUrl + '/api/device';
+ return req;
+ }
+
+ function getDevices() {
+ var req = getDevicesReq();
return $http(req);
}
- function getDevice(deviceId) {
+ function getDeviceReq(deviceId) {
var req = {};
req.method = 'get';
req.url = baseUrl + '/api/device/:deviceId';
req.url = req.url.replace(/:deviceId/, deviceId);
+ return req;
+ }
+
+ function getDevice(deviceId) {
+ var req = getDeviceReq(deviceId);
return $http(req);
}
- function patchDevice(deviceId, payload) {
+ function patchDeviceReq(deviceId, payload) {
var req = {};
req.method = 'patch';
req.url = baseUrl + '/api/device/:deviceId';
req.url = req.url.replace(/:deviceId/, deviceId);
req.data = payload;
+ return req;
+ }
+
+ function patchDevice(deviceId, payload) {
+ var req = patchDeviceReq(deviceId, payload);
return $http(req);
}
- function getValues(propertyId) {
+ function getValuesReq(propertyId) {
var req = {};
req.method = 'get';
req.url = baseUrl + '/api/property/:propertyId/values';
req.url = req.url.replace(/:propertyId/, propertyId);
+ return req;
+ }
+
+ function getValues(propertyId) {
+ var req = getValuesReq(propertyId);
return $http(req);
}
- function patchProperty(propertyId, payload) {
+ function patchPropertyReq(propertyId, payload) {
var req = {};
req.method = 'patch';
req.url = baseUrl + '/api/property/:propertyId/values';
req.url = req.url.replace(/:propertyId/, propertyId);
req.data = payload;
+ return req;
+ }
+
+ function patchProperty(propertyId, payload) {
+ var req = patchPropertyReq(propertyId, payload);
return $http(req);
}
return {
+ getDevicesReq: getDevicesReq,
getDevices: getDevices,
+ getDeviceReq: getDeviceReq,
getDevice: getDevice,
+ patchDeviceReq: patchDeviceReq,
patchDevice: patchDevice,
+ getValuesReq: getValuesReq,
getValues: getValues,
+ patchPropertyReq: patchPropertyReq,
patchProperty: patchProperty
};
}
diff --git a/web/static/app/app.js b/web/static/app/app.js
deleted file mode 100644
index 22c7f83..0000000
--- a/web/static/app/app.js
+++ /dev/null
@@ -1,179 +0,0 @@
-(function () {
- function FrontPageController(devices) {
- var ctrl = this;
-
- ctrl.devices = devices.data.devices;
- }
-
- function DeviceController($uibModal, device, DillerRpc) {
- var ctrl = this;
-
- ctrl.device = device.data.device;
-
- ctrl.propertyChunks = _(ctrl.device.properties).sortByAll(['name', 'key']).chunk(3).value();
-
- ctrl.editDeviceAttribute = function (attributeName) {
- var outer = ctrl;
- $uibModal.open({
- controller: function ($uibModalInstance) {
- var ctrl = this;
-
- ctrl.title = 'Edit device ' + attributeName;
- ctrl.label = attributeName.substr(0, 1).toUpperCase() + attributeName.substr(1);
- ctrl.value = outer.device[attributeName];
-
- ctrl.update = function () {
- DillerRpc.patchDevice(outer.device.id, {attribute: attributeName, value: ctrl.value})
- .then(function (res) {
- outer.device = res.data.device;
- $uibModalInstance.close({});
- }, function (res) {
- ctrl.error = res.data.message;
- });
- };
- },
- controllerAs: 'ctrl',
- bindToController: true,
- templateUrl: 'app/templates/edit-attribute.modal.html'
- });
- }
- }
-
- function PropertyController($timeout, $route, $uibModal, DillerRpc, device, values) {
- var ctrl = this;
-
- function updateData(device) {
- ctrl.device = device.data.device;
- ctrl.property = _.find(ctrl.device.properties, {id: $route.current.params.propertyId});
- }
- updateData(device);
- ctrl.values = values.data.values;
-
- var refreshPromise;
- ctrl.refresh = function () {
- $timeout.cancel(refreshPromise);
- refreshPromise = $timeout(function () {
- ctrl.loading = true;
- }, 200);
-
- DillerRpc.getValues($route.current.params.propertyId).then(function (res) {
- ctrl.values = res.data.values;
- ctrl.loading = false;
- $timeout.cancel(refreshPromise);
- })
- };
-
- ctrl.editPropertyAttribute = function (attributeName) {
- var outer = ctrl;
- $uibModal.open({
- controller: function ($uibModalInstance) {
- var ctrl = this;
-
- ctrl.title = 'Edit property ' + attributeName;
- ctrl.label = attributeName.substr(0, 1).toUpperCase() + attributeName.substr(1);
- ctrl.value = outer.property[attributeName];
-
- ctrl.update = function () {
- DillerRpc.patchProperty(outer.property.id, {attribute: attributeName, value: ctrl.value})
- .then(function (res) {
- updateData(res);
- $uibModalInstance.close({});
- }, function (res) {
- ctrl.error = res.data.message;
- });
- };
- },
- controllerAs: 'ctrl',
- bindToController: true,
- templateUrl: 'app/templates/edit-attribute.modal.html'
- });
- }
- }
-
- function TimestampFilter() {
- return function (value) {
- if (!value) {
- return;
- }
-
- return moment(value).startOf('second').fromNow();
- }
- }
-
- function DlTimestampDirective() {
- return {
- restrict: 'E',
- scope: {
- value: '='
- },
- replace: true,
- template: '{{value|timestamp}}'
- };
- }
-
- function DlDotsDirective() {
- return {
- restrict: 'E',
- scope: {
- value: '='
- },
- replace: true,
- template: '...\n'
- };
- }
-
- function config($routeProvider) {
- $routeProvider
- .when('/', {
- controller: FrontPageController,
- controllerAs: 'ctrl',
- templateUrl: 'app/templates/front-page.html',
- resolve: {
- devices: DillerRpcResolve.getDevices
- }
- })
- .when('/device/:deviceId', {
- controller: DeviceController,
- controllerAs: 'ctrl',
- templateUrl: 'app/templates/device.html',
- resolve: {
- device: DillerRpcResolve.getDevice
- }
- })
- .when('/device/:deviceId/property/:propertyId', {
- controller: PropertyController,
- controllerAs: 'ctrl',
- templateUrl: 'app/templates/property.html',
- resolve: {
- device: DillerRpcResolve.getDevice,
- values: DillerRpcResolve.getValues
- }
- })
- .otherwise({
- redirectTo: '/'
- });
- }
-
- function run($log) {
- window.console = $log;
- }
-
- function DillerConfig() {
- var head = document.getElementsByTagName('head')[0];
- var base = head.getElementsByTagName('base')[0];
- var baseUrl = base.href.replace(/\/$/, '');
- return {
- baseUrl: baseUrl
- };
- }
-
- angular
- .module('Diller', ['ngRoute', 'ui.bootstrap'])
- .config(config)
- .run(run)
- .filter('timestamp', TimestampFilter)
- .directive('dlTimestamp', DlTimestampDirective)
- .directive('dlDots', DlDotsDirective)
- .service('DillerConfig', DillerConfig)
- .service('DillerRpc', DillerRpc);
-})();
diff --git a/web/static/app/diller/client.js b/web/static/app/diller/client.js
new file mode 100644
index 0000000..6641ffb
--- /dev/null
+++ b/web/static/app/diller/client.js
@@ -0,0 +1,72 @@
+(function () {
+ function extractData(res) {
+ return res && res.data;
+ }
+
+ function Property($http, DillerRpc, propertyId) {
+ function getInterval(interval) {
+
+ // moment().subtract(24, 'hour')
+ var req = DillerRpc.getValuesReq(propertyId);
+ req.params = {
+ from: interval.getFrom().toISOString()
+ };
+ return $http(req).then(extractData);
+ }
+
+ /** @lends Property.prototype */
+ return {
+ getInterval: getInterval
+ };
+ }
+
+ function Device($http, DillerRpc, deviceId) {
+ var properties = {};
+
+ /**
+ * @param propertyId
+ * @returns {Property}
+ */
+ function getProperty(propertyId) {
+ var p = properties[propertyId];
+
+ if (!p) {
+ p = new Property($http, DillerRpc, propertyId);
+ properties[propertyId] = p;
+ }
+
+ return p;
+ }
+
+ /** @lends Device.prototype */
+ return {
+ getProperty: getProperty
+ };
+ }
+
+ function DillerClient($timeout, $http, DillerRpc) {
+
+ var devices = {};
+
+ function getDevice(deviceId) {
+ var d = devices[deviceId];
+
+ if (!d) {
+ d = new Device($http, DillerRpc, deviceId);
+ devices[deviceId] = d;
+ }
+
+ return d;
+ }
+
+ /** @lends DillerClient.prototype */
+ return {
+ getDevice: getDevice
+ };
+ }
+
+ angular
+ .module('diller.client', [])
+ .service('DillerClient', DillerClient)
+ .service('DillerRpc', window.DillerRpc);
+})();
diff --git a/web/static/app/diller/global.js b/web/static/app/diller/global.js
new file mode 100644
index 0000000..0da8da7
--- /dev/null
+++ b/web/static/app/diller/global.js
@@ -0,0 +1,60 @@
+(function () {
+ var Diller = window.Diller = window.Diller || {};
+
+ function toDate(unknown) {
+ if (!unknown) {
+ return undefined;
+ } else if (moment.isMoment(unknown)) {
+ return unknown;
+ } else if (typeof unknown === 'string') {
+ unknown = unknown.trim();
+
+ if (unknown == '') {
+ return moment();
+ }
+
+ return moment(unknown, 'YYYY-MM-DDTHH:mm.sssZ');
+ } else if (typeof unknown === 'string' || moment.isDate(unknown)) {
+ return moment(unknown);
+ } else {
+ return undefined;
+ }
+ }
+
+ Diller.Interval = function (from, to) {
+ var f = toDate(from),
+ t = toDate(to);
+
+ if (f.isAfter(t)) {
+ var tmp = f;
+ f = t;
+ t = tmp;
+ }
+
+ return {
+ getFrom: function () {
+ return f || moment();
+ },
+ getTo: function () {
+ return t || moment();
+ },
+ toString: function () {
+ return 'yo'
+ }
+ };
+ };
+
+ Diller.Interval.create = function (value) {
+ if (value instanceof Diller.Interval) {
+ return value;
+ }
+
+ return new Diller.Interval.hours(24);
+ };
+
+ Diller.Interval.hours = function (hours) {
+ var to = moment();
+ var from = to.subtract(hours, 'hours');
+ return new Diller.Interval(from, to);
+ };
+})();
diff --git a/web/static/app/diller/line-chart.js b/web/static/app/diller/line-chart.js
new file mode 100644
index 0000000..69b6f77
--- /dev/null
+++ b/web/static/app/diller/line-chart.js
@@ -0,0 +1,62 @@
+(function () {
+
+ var isoFormat = 'YYYY-MM-DDTHH:mm:ss';
+
+ function DlLineChartDirective($timeout, DillerClient) {
+ var id_seq = 0;
+
+ return {
+ restrict: 'E',
+ scope: {
+ device: '=',
+ property: '=',
+ value: '=',
+ interval: '='
+ },
+ replace: true,
+ template: '',
+ link: function (scope, element, attrs) {
+ var elementId = element.attr('id');
+ if (!elementId) {
+ elementId = 'dl-line-chart-' + id_seq++;
+ element.attr('id', elementId);
+ }
+
+ var deviceId = scope.device;
+ var propertyId = scope.property;
+ var interval = Diller.Interval.create(scope.interval);
+ var property = DillerClient.getDevice(deviceId).getProperty(propertyId);
+
+ var options = {
+ axisX: {
+ showLabel: true,
+ showGrid: true,
+ labelInterpolationFnc: function (value, index) {
+ return index % 4 === 0 ? value.format('HH:mm') : null;
+ }
+ }
+ };
+
+ property.getInterval(interval).then(function (data) {
+ var avgs = _.pluck(data.values, 'avg');
+ var timestamps = _.map(data.values, function (row) {
+ return moment(row.timestamp, isoFormat);
+ });
+
+ var chartData = {
+ labels: timestamps,
+ series: [
+ avgs
+ ]
+ };
+
+ var chart = new Chartist.Line('#' + elementId, chartData, options);
+ });
+ }
+ };
+ }
+
+ angular
+ .module('diller.line-chart', ['diller.client'])
+ .directive('dlLineChart', DlLineChartDirective);
+})();
diff --git a/web/static/app/diller/web.js b/web/static/app/diller/web.js
new file mode 100644
index 0000000..4b173ad
--- /dev/null
+++ b/web/static/app/diller/web.js
@@ -0,0 +1,236 @@
+(function () {
+ var isoFormat = 'YYYY-MM-DDTHH:mm:ss';
+
+ function FrontPageController(devices) {
+ var ctrl = this;
+
+ ctrl.devices = devices.data.devices;
+ }
+
+ function DeviceController($uibModal, device, DillerRpc) {
+ var ctrl = this;
+
+ ctrl.device = device.data.device;
+
+ ctrl.propertyChunks = _(ctrl.device.properties).sortByAll(['name', 'key']).chunk(3).value();
+
+ ctrl.editDeviceAttribute = function (attributeName) {
+ var outer = ctrl;
+ $uibModal.open({
+ controller: function ($uibModalInstance) {
+ var ctrl = this;
+
+ ctrl.title = 'Edit device ' + attributeName;
+ ctrl.label = attributeName.substr(0, 1).toUpperCase() + attributeName.substr(1);
+ ctrl.value = outer.device[attributeName];
+
+ ctrl.update = function () {
+ DillerRpc.patchDevice(outer.device.id, {attribute: attributeName, value: ctrl.value})
+ .then(function (res) {
+ outer.device = res.data.device;
+ $uibModalInstance.close({});
+ }, function (res) {
+ ctrl.error = res.data.message;
+ });
+ };
+ },
+ controllerAs: 'ctrl',
+ bindToController: true,
+ templateUrl: 'app/templates/edit-attribute.modal.html'
+ });
+ }
+ }
+
+ function PropertyController($timeout, $route, $uibModal, DillerRpc, device, values) {
+ var ctrl = this;
+
+ function updateData(device) {
+ ctrl.device = device.data.device;
+ ctrl.property = _.find(ctrl.device.properties, {id: $route.current.params.propertyId});
+ }
+ updateData(device);
+ ctrl.values = values.data.values;
+
+ var refreshPromise;
+ ctrl.refresh = function () {
+ $timeout.cancel(refreshPromise);
+ refreshPromise = $timeout(function () {
+ ctrl.loading = true;
+ }, 200);
+
+ DillerRpc.getValues($route.current.params.propertyId).then(function (res) {
+ ctrl.values = res.data.values;
+ ctrl.loading = false;
+ $timeout.cancel(refreshPromise);
+ })
+ };
+
+ ctrl.editPropertyAttribute = function (attributeName) {
+ var outer = ctrl;
+ $uibModal.open({
+ controller: function ($uibModalInstance) {
+ var ctrl = this;
+
+ ctrl.title = 'Edit property ' + attributeName;
+ ctrl.label = attributeName.substr(0, 1).toUpperCase() + attributeName.substr(1);
+ ctrl.value = outer.property[attributeName];
+
+ ctrl.update = function () {
+ DillerRpc.patchProperty(outer.property.id, {attribute: attributeName, value: ctrl.value})
+ .then(function (res) {
+ updateData(res);
+ $uibModalInstance.close({});
+ }, function (res) {
+ ctrl.error = res.data.message;
+ });
+ };
+ },
+ controllerAs: 'ctrl',
+ bindToController: true,
+ templateUrl: 'app/templates/edit-attribute.modal.html'
+ });
+ };
+
+ ctrl.interval = Diller.Interval.hours(5);
+
+ // ctrl.values24h = values24h.data.values;
+
+ function chartist() {
+ var avgs = _.pluck(ctrl.values24h, 'avg');
+ var timestamps = _.map(ctrl.values24h, function(row) {
+ var m = moment(row.timestamp, isoFormat);
+ return m.format('HH:mm');
+ });
+
+ var data = {
+ labels: timestamps,
+ series: [
+ avgs
+ ]
+ };
+
+ var options = {
+ axisX: {
+ showLabel: false,
+ showGrid: false
+ }
+ };
+
+// options.lineSmooth = Chartist.Interpolation.cardinal({
+// fillHoles: true,
+// })
+
+ options.axisX = {
+ showLabel: true,
+ showGrid: true,
+ labelInterpolationFnc: function(value, index) {
+ return index % 4 === 0 ? value : null;
+ }
+ };
+
+ var chartData = {
+ series: [[]],
+ labels: []
+ };
+
+ // var chart = new Chartist.Line('#values-chart', chartData, options);
+ // console.log('chart', chart);
+/*
+ $timeout(function() {
+ chartData = data;
+ // console.log(data);
+ var chart = new Chartist.Line('#values-chart', chartData, options);
+ console.log('chart', chart);
+ });
+*/
+ }
+
+ // chartist();
+ }
+
+ function TimestampFilter() {
+ return function (value) {
+ if (!value) {
+ return;
+ }
+
+ return moment(value).startOf('second').fromNow();
+ }
+ }
+
+ function DlTimestampDirective() {
+ return {
+ restrict: 'E',
+ scope: {
+ value: '='
+ },
+ replace: true,
+ template: '{{value|timestamp}}'
+ };
+ }
+
+ function DlDotsDirective() {
+ return {
+ restrict: 'E',
+ scope: {
+ value: '='
+ },
+ replace: true,
+ template: '...\n'
+ };
+ }
+
+ function config($routeProvider) {
+ $routeProvider
+ .when('/', {
+ controller: FrontPageController,
+ controllerAs: 'ctrl',
+ templateUrl: 'app/templates/front-page.html',
+ resolve: {
+ devices: DillerRpcResolve.getDevices
+ }
+ })
+ .when('/device/:deviceId', {
+ controller: DeviceController,
+ controllerAs: 'ctrl',
+ templateUrl: 'app/templates/device.html',
+ resolve: {
+ device: DillerRpcResolve.getDevice
+ }
+ })
+ .when('/device/:deviceId/property/:propertyId', {
+ controller: PropertyController,
+ controllerAs: 'ctrl',
+ templateUrl: 'app/templates/property.html',
+ resolve: {
+ device: DillerRpcResolve.getDevice,
+ values: DillerRpcResolve.getValues
+ }
+ })
+ .otherwise({
+ redirectTo: '/'
+ });
+ }
+
+ function run($log) {
+ window.console = $log;
+ }
+
+ function DillerConfig() {
+ var head = document.getElementsByTagName('head')[0];
+ var base = head.getElementsByTagName('base')[0];
+ var baseUrl = base.href.replace(/\/$/, '');
+ return {
+ baseUrl: baseUrl
+ };
+ }
+
+ angular
+ .module('diller.web', ['ngRoute', 'ui.bootstrap', 'diller.line-chart'])
+ .config(config)
+ .run(run)
+ .filter('timestamp', TimestampFilter)
+ .directive('dlTimestamp', DlTimestampDirective)
+ .directive('dlDots', DlDotsDirective)
+ .service('DillerConfig', DillerConfig);
+})();
diff --git a/web/static/app/templates/property.html b/web/static/app/templates/property.html
index 969734b..3f3424f 100644
--- a/web/static/app/templates/property.html
+++ b/web/static/app/templates/property.html
@@ -20,20 +20,25 @@
-
- - Key
- -
- {{ctrl.property.key}}
-
-
+
+
+ - Key
+ -
+ {{ctrl.property.key}}
+
+
- - Created
- -
- {{ctrl.property.created_timestamp | date:'medium'}}
-
-
-
+
- Created
+
-
+ {{ctrl.property.created_timestamp | date:'medium'}}
+
+
+
+
+
+
+
diff --git a/web/templates/index.jade b/web/templates/index.jade
index 1b2adbc..84c31dc 100644
--- a/web/templates/index.jade
+++ b/web/templates/index.jade
@@ -17,14 +17,19 @@ html(lang='en')
script(src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js", type="application/javascript")
script(src="bower_components/lodash/lodash.js", type="application/javascript")
script(src="bower_components/moment/moment.js", type="application/javascript")
+ script(src="bower_components/chartist/dist/chartist.js", type="application/javascript")
script(src="app/DillerRpc.js", type="application/javascript")
- script(src="app/app.js", type="application/javascript")
+ script(src="app/diller/global.js", type="application/javascript")
+ script(src="app/diller/client.js", type="application/javascript")
+ script(src="app/diller/web.js", type="application/javascript")
+ script(src="app/diller/line-chart.js", type="application/javascript")
link(href="app/app.css", rel="stylesheet")
link(rel="stylesheet", href="bower_components/font-awesome/css/font-awesome.min.css")
+ link(rel="stylesheet", href="bower_components/chartist/dist/chartist.css")
- body(ng-app="Diller")
+ body(ng-app="diller.web")
.container
nav.navbar.navbar-dark.bg-inverse
a.navbar-brand(href='#/') Diller
diff --git a/web/templates/wat.html b/web/templates/wat.html
index fd504a7..0e77077 100644
--- a/web/templates/wat.html
+++ b/web/templates/wat.html
@@ -17,7 +17,7 @@
-
+
Loading Diller ...
--
cgit v1.2.3