aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2015-10-31 12:04:28 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2015-10-31 12:04:28 +0100
commit02d6e77bd180cbbf6f7f6e1a69c670e922d8204d (patch)
treec55847b71e589f79ce6537f2b61f3691fe8bf220
parentdd72e7992f126c4a42a0791228400738072f297d (diff)
downloaddiller-server-02d6e77bd180cbbf6f7f6e1a69c670e922d8204d.tar.gz
diller-server-02d6e77bd180cbbf6f7f6e1a69c670e922d8204d.tar.bz2
diller-server-02d6e77bd180cbbf6f7f6e1a69c670e922d8204d.tar.xz
diller-server-02d6e77bd180cbbf6f7f6e1a69c670e922d8204d.zip
web:
o Starting on edit button for device name.
-rw-r--r--.editorconfig9
-rw-r--r--bower.json6
-rw-r--r--gulpfile.js11
-rw-r--r--src/Diller.js23
-rw-r--r--src/DillerDao.js33
-rw-r--r--src/web/DillerWeb.js76
-rw-r--r--web/static/app/DillerRpc.js13
-rw-r--r--web/static/app/app.js29
-rw-r--r--web/static/app/templates/device-edit-attribute.modal.html13
-rw-r--r--web/static/app/templates/device.html8
-rw-r--r--web/templates/index.jade4
11 files changed, 199 insertions, 26 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..118d2d6
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+[*.{js,py,jade,css}]
+indent_size = 2
diff --git a/bower.json b/bower.json
index d6219e6..60b30ff 100644
--- a/bower.json
+++ b/bower.json
@@ -13,10 +13,12 @@
"tests"
],
"dependencies": {
+ "lodash": "~3.10.1",
+ "moment": "~2.10.6",
"angular": "~1.4.7",
"angular-route": "~1.4.7",
"bootstrap": "4.0.0-alpha",
- "lodash": "~3.10.1",
- "moment": "~2.10.6"
+ "angular-bootstrap": "~0.14.3",
+ "font-awesome": "fontawesome#~4.4.0"
}
}
diff --git a/gulpfile.js b/gulpfile.js
index 724a0b1..1e98a86 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -46,9 +46,7 @@ gulp.task('diller-mqtt', function () {
],
env: {NODE_ENV: 'development'},
tasks: ['diller-mqtt-reload'],
- stdout: false,
- //readable: false,
- a: ''
+ stdout: false
}).on('readable', function () {
return readable(state, this);
});
@@ -58,6 +56,8 @@ gulp.task('diller-web-reload', function () {
});
gulp.task('diller-web', ['bower'], function () {
+ var state = {};
+
nodemon({
script: 'diller-web.js',
delay: 500,
@@ -69,7 +69,10 @@ gulp.task('diller-web', ['bower'], function () {
'src/mqtt/'
],
env: {NODE_ENV: 'development'},
- tasks: ['diller-web-reload']
+ tasks: ['diller-web-reload'],
+ stdout: false
+ }).on('readable', function () {
+ return readable(state, this);
});
});
diff --git a/src/Diller.js b/src/Diller.js
index e73bf46..d52e3f1 100644
--- a/src/Diller.js
+++ b/src/Diller.js
@@ -1,5 +1,11 @@
var di = require('di');
+/**
+ * @param config DillerConfig
+ * @param db DillerDb
+ * @returns {{onMessage: onMessage, updateDeviceName: updateDeviceName}}
+ * @constructor
+ */
function Diller(config, db) {
var log = config.log();
@@ -51,6 +57,20 @@ function Diller(config, db) {
});
}
+ function updateDeviceName(deviceId, name) {
+ log.info('Updating device name', {deviceId: deviceId, name: name});
+ return db()
+ .tx(function (tx) {
+ var dao = new DillerDao(tx);
+
+ return dao.updateDevice(deviceId, {name: name})
+ .then(function (res) {
+ log.info('Device name updated', {deviceId: deviceId, name: name});
+ return res;
+ });
+ })
+ }
+
function onMessage(topic, message, payload) {
var parts = topic.split(/\//);
@@ -118,7 +138,8 @@ function Diller(config, db) {
}
return {
- onMessage: onMessage
+ onMessage: onMessage,
+ updateDeviceName: updateDeviceName
}
}
diff --git a/src/DillerDao.js b/src/DillerDao.js
index cae80d7..2133dfc 100644
--- a/src/DillerDao.js
+++ b/src/DillerDao.js
@@ -1,3 +1,5 @@
+var _ = require('lodash');
+
function DillerDao(tx) {
var deviceColumns = 'id, key, created_timestamp';
@@ -24,6 +26,30 @@ function DillerDao(tx) {
return tx.one("INSERT INTO device(id, key, created_timestamp) VALUES(DEFAULT, $1, CURRENT_TIMESTAMP) RETURNING " + deviceColumns, key);
}
+ function updateDevice(id, attributes) {
+
+ var values = [id];
+ var i = 2;
+ var fields = _.map(attributes, function (value, name) {
+ console.log('name', name, 'value', value, 'i', i);
+ if (name == 'name') {
+ values.push(value);
+ return 'name = $' + i++;
+ }
+ });
+
+ if (fields.length == 0) {
+ return; // TODO: return an empty promise;
+ }
+
+ fields = _.collect(fields);
+
+ var x = 'UPDATE device SET ' + fields.join(', ') + ' WHERE id = $1';
+ console.log('x', x);
+ console.log('values', values);
+ return tx.none(x, values);
+ }
+
// -------------------------------------------------------------------------------------------------------------------
// Device Property
// -------------------------------------------------------------------------------------------------------------------
@@ -70,10 +96,10 @@ function DillerDao(tx) {
function updateHourAggregatesForProperty(propertyId, timestamp) {
return tx.none('DELETE FROM value_by_hour WHERE property=$1 AND timestamp=DATE_TRUNC(\'hour\', $2::TIMESTAMPTZ)', [propertyId, timestamp])
- .then(function() {
+ .then(function () {
return tx.oneOrNone('INSERT INTO value_by_hour(property, timestamp, count, max, min, avg) ' +
'SELECT property, DATE_TRUNC(\'hour\', timestamp) AS timestamp, COUNT(value_numeric) AS count, MAX(value_numeric) AS max, MIN(value_numeric) AS min, AVG(value_numeric) AS avg ' +
- 'FROM value WHERE property=$1 AND DATE_TRUNC(\'hour\', timestamp)=DATE_TRUNC(\'hour\', $2::TIMESTAMPTZ) AND value_numeric IS NOT NULL ' +
+ 'FROM value WHERE property=$1 AND DATE_TRUNC(\'hour\', timestamp)=DATE_TRUNC(\'hour\', $2::TIMESTAMPTZ) AND value_numeric IS NOT NULL ' +
'GROUP BY property, DATE_TRUNC(\'hour\', timestamp) ' +
'RETURNING *;', [propertyId, timestamp]);
});
@@ -81,7 +107,7 @@ function DillerDao(tx) {
function updateMinuteAggregatesForProperty(propertyId, timestamp) {
return tx.none('DELETE FROM value_by_minute WHERE property=$1 AND timestamp=DATE_TRUNC(\'minute\', $2::TIMESTAMPTZ)', [propertyId, timestamp])
- .then(function() {
+ .then(function () {
return tx.oneOrNone('INSERT INTO value_by_minute(property, timestamp, count, max, min, avg) ' +
'SELECT property, DATE_TRUNC(\'minute\', timestamp) AS timestamp, COUNT(value_numeric) AS count, MAX(value_numeric) AS max, MIN(value_numeric) AS min, AVG(value_numeric) AS avg ' +
'FROM value ' +
@@ -96,6 +122,7 @@ function DillerDao(tx) {
deviceById: deviceById,
deviceByKey: deviceByKey,
insertDevice: insertDevice,
+ updateDevice: updateDevice,
devicePropertyById: devicePropertyById,
devicePropertyByDeviceIdAndKey: devicePropertyByDeviceIdAndKey,
diff --git a/src/web/DillerWeb.js b/src/web/DillerWeb.js
index c261af7..95bcbb6 100644
--- a/src/web/DillerWeb.js
+++ b/src/web/DillerWeb.js
@@ -14,16 +14,26 @@ function DillerWeb(diller, db, config) {
var calls = [];
var app;
+ function genericErrorHandler(res) {
+ return function (data) {
+ log.warn('fail', data);
+ return res.status(500).json({message: 'fail', data: data})
+ }
+ }
+
function getDevices(req, res) {
db().tx(function (pg) {
var dao = new DillerDao(pg);
return dao.devices();
}).then(function (devices) {
res.json({devices: devices});
- }, function (err) {
- log.warn('fail', err);
- res.status(500).json({message: 'fail'});
- });
+ }, genericErrorHandler(res));
+ }
+
+ function deviceResponse(data) {
+ var device = data[0];
+ device.properties = data[1];
+ return {device: device};
}
function getDevice(req, res) {
@@ -36,13 +46,36 @@ function DillerWeb(diller, db, config) {
dao.devicePropertiesByDeviceId(deviceId)]
);
}).then(function (data) {
+ res.json(deviceResponse(data));
+ }, genericErrorHandler(res));
+ }
+
+ function patchDevice(req, res) {
+ db().tx(function (tx) {
+ var deviceId = req.params.deviceId;
+
+ var body = req.body;
+
+ if (body.attribute == 'name') {
+ diller.updateDeviceName(deviceId, body.value)
+ .then(function () {
+ var dao = new DillerDao(tx);
+ return tx.batch([
+ dao.deviceById(deviceId),
+ dao.devicePropertiesByDeviceId(deviceId)]
+ );
+ })
+ .then(function (data) {
+ res.json(deviceResponse(data));
+ }, genericErrorHandler(res));
+ } else {
+ res.status(400).json({message: 'Required keys: "attribute" and "value".'});
+ }
+ }).then(function (data) {
var device = data[0];
device.properties = data[1];
res.json({device: device});
- }, function (err) {
- log.warn('fail', err);
- res.status(500).json({message: 'fail'});
- });
+ }, genericErrorHandler(res));
}
function getValues(req, res) {
@@ -76,19 +109,34 @@ function DillerWeb(diller, db, config) {
api[method](path, callback);
var layer = _.last(api.stack);
+ var methodWithPayloads = {
+ put: true,
+ post: true,
+ patch: true
+ };
+
+ var keys = _.map(layer.keys, function (key) {
+ return key.name;
+ });
+
+ var hasPayload = methodWithPayloads[method];
+ if (hasPayload) {
+ keys.push('payload');
+ }
+
calls.push({
name: name,
method: method,
path: path,
layer: layer,
- keys: _.map(layer.keys, function (key) {
- return key.name;
- })
+ hasPayload: hasPayload,
+ keys: keys
});
}
addApi('getDevices', 'get', '/device', getDevices);
addApi('getDevice', 'get', '/device/:deviceId', getDevice);
+ addApi('patchDevice', 'patch', '/device/:deviceId', patchDevice);
addApi('getValues', 'get', '/property/:propertyId/values', getValues);
var templates = express.Router();
@@ -116,8 +164,6 @@ function DillerWeb(diller, db, config) {
s += _.map(calls, function (call) {
- //console.error(call);
- //console.error('call.layer', call.layer);
var s = ' function ' + call.name + '(' + call.keys.join(', ') + ') {\n' +
' var req = {};\n' +
' req.method = \'' + call.method + '\';\n' +
@@ -127,6 +173,10 @@ function DillerWeb(diller, db, config) {
return ' req.url = req.url.replace(/:' + key.name + '/, ' + key.name + ');\n'
}).join('\n');
+ if (call.hasPayload) {
+ s += ' req.data = payload;\n';
+ }
+
s +=
' return $http(req);\n' +
' }\n';
diff --git a/web/static/app/DillerRpc.js b/web/static/app/DillerRpc.js
index de90aee..b1939f4 100644
--- a/web/static/app/DillerRpc.js
+++ b/web/static/app/DillerRpc.js
@@ -16,6 +16,15 @@ function DillerRpc($http, DillerConfig) {
return $http(req);
}
+ function patchDevice(deviceId, payload) {
+ var req = {};
+ req.method = 'patch';
+ req.url = baseUrl + '/api/device/:deviceId';
+ req.url = req.url.replace(/:deviceId/, deviceId);
+ req.data = payload;
+ return $http(req);
+ }
+
function getValues(propertyId) {
var req = {};
req.method = 'get';
@@ -27,6 +36,7 @@ function DillerRpc($http, DillerConfig) {
return {
getDevices: getDevices,
getDevice: getDevice,
+ patchDevice: patchDevice,
getValues: getValues
};
}
@@ -38,6 +48,9 @@ DillerRpcResolve.getDevices = function(DillerRpc) {
DillerRpcResolve.getDevice = function(DillerRpc, $route) {
return DillerRpc.getDevice($route.current.params.deviceId);
};
+DillerRpcResolve.patchDevice = function(DillerRpc, $route) {
+ return DillerRpc.patchDevice($route.current.params.deviceId, $route.current.params.payload);
+};
DillerRpcResolve.getValues = function(DillerRpc, $route) {
return DillerRpc.getValues($route.current.params.propertyId);
};
diff --git a/web/static/app/app.js b/web/static/app/app.js
index 6fa1f71..134afc8 100644
--- a/web/static/app/app.js
+++ b/web/static/app/app.js
@@ -5,10 +5,35 @@
ctrl.devices = devices.data.devices;
}
- function DeviceController(device) {
+ function DeviceController($uibModal, device, DillerRpc) {
var ctrl = this;
ctrl.device = device.data.device;
+
+ ctrl.editDeviceAttribute = function (attributeName) {
+ var outer = ctrl;
+ $uibModal.open({
+ controller: function ($uibModalInstance) {
+ var ctrl = this;
+
+ ctrl.attributeName = attributeName;
+ ctrl.value = outer.device[attributeName];
+
+ ctrl.value = 'yoyo';
+
+ ctrl.update = function () {
+
+ DillerRpc.patchDevice(outer.device.id, {attribute: attributeName, value: ctrl.value})
+ .then(function (res) {
+ $uibModalInstance.close({});
+ });
+ };
+ },
+ controllerAs: 'ctrl',
+ bindToController: true,
+ templateUrl: 'app/templates/device-edit-attribute.modal.html'
+ });
+ }
}
function PropertyController($timeout, $route, DillerRpc, device, values) {
@@ -110,7 +135,7 @@
}
angular
- .module('Diller', ['ngRoute'])
+ .module('Diller', ['ngRoute', 'ui.bootstrap'])
.config(config)
.filter('timestamp', TimestampFilter)
.directive('dlTimestamp', DlTimestampDirective)
diff --git a/web/static/app/templates/device-edit-attribute.modal.html b/web/static/app/templates/device-edit-attribute.modal.html
new file mode 100644
index 0000000..e471e5f
--- /dev/null
+++ b/web/static/app/templates/device-edit-attribute.modal.html
@@ -0,0 +1,13 @@
+<div class="modal-header">
+ <button type="button" class="close" ng-click="$dismiss()">
+ <span>&times;</span>
+ </button>
+ <h4 class="modal-title">Edit device {{attributeName}}</h4>
+</div>
+<div class="modal-body">
+ <p>One fine body&hellip;</p>
+</div>
+<div class="modal-footer">
+ <button type="button" class="btn btn-secondary" ng-click="$dismiss()">Cancel</button>
+ <button type="button" class="btn btn-primary" ng-click="ctrl.update()">Update</button>
+</div>
diff --git a/web/static/app/templates/device.html b/web/static/app/templates/device.html
index b8936f3..d126f1d 100644
--- a/web/static/app/templates/device.html
+++ b/web/static/app/templates/device.html
@@ -59,7 +59,13 @@
<dd class="col-sm-9">{{ctrl.device.created_timestamp | date}}</dd>
<dt class="col-sm-3">Name</dt>
- <dd class="col-sm-9">&nbsp;{{ctrl.device.name}}</dd>
+ <dd class="col-sm-9">
+ &nbsp;{{ctrl.device.name}}
+
+ <a ng-click="ctrl.editDeviceAttribute('name')" class="pull-right">
+ <i class="fa fa-edit"/>
+ </a>
+ </dd>
<dt class="col-sm-3">Description</dt>
<dd class="col-sm-9">&nbsp;{{ctrl.device.description}}</dd>
diff --git a/web/templates/index.jade b/web/templates/index.jade
index 6dad0f5..1b2adbc 100644
--- a/web/templates/index.jade
+++ b/web/templates/index.jade
@@ -13,6 +13,8 @@ html(lang='en')
link(rel="stylesheet", href="bower_components/bootstrap/dist/css/bootstrap.css")
script(src="bower_components/angular/angular.js", type="application/javascript")
script(src="bower_components/angular-route/angular-route.js", type="application/javascript")
+ script(src="bower_components/angular-bootstrap/ui-bootstrap.js", type="application/javascript")
+ 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")
@@ -20,6 +22,8 @@ html(lang='en')
script(src="app/app.js", type="application/javascript")
link(href="app/app.css", rel="stylesheet")
+ link(rel="stylesheet", href="bower_components/font-awesome/css/font-awesome.min.css")
+
body(ng-app="Diller")
.container
nav.navbar.navbar-dark.bg-inverse