diff options
-rw-r--r-- | .bowerrc | 3 | ||||
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | bower.json | 21 | ||||
-rw-r--r-- | diller-web.js | 10 | ||||
-rw-r--r-- | diller.js | 67 | ||||
-rw-r--r-- | package.json | 7 | ||||
-rw-r--r-- | src/Diller.js | 37 | ||||
-rw-r--r-- | src/DillerConfig.js | 67 | ||||
-rw-r--r-- | src/DillerDao.js | 63 | ||||
-rw-r--r-- | src/DillerDb.js | 29 | ||||
-rw-r--r-- | src/config.js | 18 | ||||
-rw-r--r-- | src/mqtt/DillerMqtt.js | 38 | ||||
-rw-r--r-- | src/web/DillerWeb.js | 158 | ||||
-rw-r--r-- | web/app/DillerRpc.js | 41 | ||||
-rw-r--r-- | web/app/app.js | 60 | ||||
-rw-r--r-- | web/app/templates/device.html | 21 | ||||
-rw-r--r-- | web/app/templates/front-page.html | 24 | ||||
-rw-r--r-- | web/app/templates/property.html | 36 | ||||
-rw-r--r-- | web/index.html | 24 |
19 files changed, 607 insertions, 118 deletions
diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..80e4029 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "web/bower_components" +} @@ -1,2 +1,3 @@ .idea node_modules +web/bower_components diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..0138bf1 --- /dev/null +++ b/bower.json @@ -0,0 +1,21 @@ +{ + "name": "diller-server", + "version": "0.0.0", + "authors": [ + "Trygve Laugstøl <trygvis@inamo.no>" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "angular": "~1.4.7", + "angular-route": "~1.4.7", + "bootstrap": "4.0.0-alpha", + "lodash": "~3.10.1" + } +} diff --git a/diller-web.js b/diller-web.js new file mode 100644 index 0000000..4cceaa1 --- /dev/null +++ b/diller-web.js @@ -0,0 +1,10 @@ +var di = require('di'); +var injector = new di.Injector(); + +var config = injector.get(require('./src/DillerConfig')); +config.configureLogging('web'); + +var dillerWeb = injector.get(require('./src/web/DillerWeb')); +dillerWeb.init(); +dillerWeb.generateRpc(); +dillerWeb.listen(); @@ -1,63 +1,8 @@ -var mqtt = require('mqtt'); -var fs = require('fs'); -var bunyan = require('bunyan'); +var di = require('di'); +var injector = new di.Injector(); -var config = require('./src/config'); +var config = injector.get(require('./src/DillerConfig')); +config.configureLogging('mqtt'); -function configureLogging(config) { - var cfg = { - name: 'main' - }; - - if (config.isProd()) { - cfg.streams = [ - { - level: 'warn', - stream: process.stdout - }, - { - level: 'debug', - path: 'log/diller.log' - } - ]; - - var stat; - try { - stat = fs.lstatSync('log'); - } catch (e) { - // Assume this to to be ENOENT - fs.mkdirSync('log'); - } - if (stat && !stat.isDirectory()) { - throw 'Not a directory: log'; - } - } - - return bunyan.createLogger(cfg); -} - -var log = configureLogging(config); - -var Diller = require('./src/Diller').Diller; - -var diller = new Diller(config, log); - -log.info('Connecting to ' + config.mqttUrl); -var mqttClient = mqtt.connect(config.mqttUrl); - -mqttClient.on('offline', function () { - log.info('offline'); -}); - -mqttClient.on('error', function (error) { - log.info('error', {error: error}); -}); - -mqttClient.on('connect', function () { - log.info('Connected'); - mqttClient.subscribe('/diller/#'); -}); - -mqttClient.on('message', function (topic, message, payload) { - diller.onMessage(topic, message, payload); -}); +var dillerMqtt = injector.get(require('./src/mqtt/DillerMqtt')); +dillerMqtt.run(); diff --git a/package.json b/package.json index 388c2f0..bfc0373 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,14 @@ "author": "", "license": "MIT", "dependencies": { + "body-parser": "^1.14.1", "bunyan": "^1.5.1", "db-migrate": "^0.9.23", + "di": "^2.0.0-pre-14", + "express": "^4.13.3", + "lodash": "^3.10.1", "mqtt": "^1.4.3", "pg": "^4.4.2", - "pg-promise": "^2.0.12", - "underscore": "^1.8.3" + "pg-promise": "^2.0.12" } } diff --git a/src/Diller.js b/src/Diller.js index eeaf726..a057054 100644 --- a/src/Diller.js +++ b/src/Diller.js @@ -1,22 +1,7 @@ -var DillerDao = require('./DillerDao'); -var pgpOptions = { - //query: function (e) { - // console.log("Query:", e.query); - // if (e.ctx) { - // // this query is executing inside a task or transaction, - // if (e.ctx.isTX) { - // // this query is inside a transaction; - // } else { - // // this query is inside a task; - // } - // - // } - //} -}; - -var pgp = require('pg-promise')(pgpOptions); - -function Diller(config, log) { +var di = require('di'); + +function Diller(config, db) { + var log = config.log(); function newValue(dao, device, property, value) { log.info('new value for device ' + device.key + '/' + property.key + ' = ' + value, { @@ -47,7 +32,7 @@ function Diller(config, log) { function updateAggregates(propertyId, timestamp) { log.info('Updating aggregates', {propertyId: propertyId, timestamp: timestamp}); - return pgp(config.postgresqlConfig) + return db() .tx(function (pg) { var dao = new DillerDao(pg); @@ -100,7 +85,7 @@ function Diller(config, log) { return; } - return pgp(config.postgresqlConfig) + return db() .tx(function (pg) { var dao = new DillerDao(pg); @@ -136,7 +121,9 @@ function Diller(config, log) { } } -//noinspection JSUnresolvedVariable -module.exports = { - Diller: Diller -}; +var DillerConfig = require('./DillerConfig'); +var DillerDao = require('./DillerDao'); +var DillerDb = require('./DillerDb'); +di.annotate(Diller, new di.Inject(DillerConfig, DillerDb)); + +module.exports = Diller; diff --git a/src/DillerConfig.js b/src/DillerConfig.js new file mode 100644 index 0000000..84afe42 --- /dev/null +++ b/src/DillerConfig.js @@ -0,0 +1,67 @@ +var fs = require('fs'); +var bunyan = require('bunyan'); + +function isProd() { + return process.env.NODE_ENV == 'prod'; +} + +var mqttUrl = process.env.MQTT_URL || 'mqtt://trygvis.io'; + +var postgresqlConfig = { + host: process.env.DB_HOST || '/var/run/postgresql', + database: process.env.DB_DATABASE || 'diller', + user: process.env.DB_USER || 'diller', + password: process.env.DB_PASSWORD || 'diller' +}; + +var log; + +function configureLogging(app) { + if (log) { + return; + } + + var cfg = { + name: app + }; + + if (isProd()) { + cfg.streams = [ + { + level: 'warn', + stream: process.stdout + }, + { + level: 'debug', + path: 'log/diller-' + app + '.log' + } + ]; + + var stat; + try { + stat = fs.lstatSync('log'); + } catch (e) { + // Assume this to to be ENOENT + fs.mkdirSync('log'); + } + if (stat && !stat.isDirectory()) { + throw 'Not a directory: log'; + } + } + + log = bunyan.createLogger(cfg); +} + +function DillerConfig() { + return { + isProd: isProd, + mqttUrl: mqttUrl, + postgresqlConfig: postgresqlConfig, + configureLogging: configureLogging, + log: function () { + return log; + } + }; +} + +module.exports = DillerConfig; diff --git a/src/DillerDao.js b/src/DillerDao.js index e0c69d3..c4d0e67 100644 --- a/src/DillerDao.js +++ b/src/DillerDao.js @@ -1,40 +1,74 @@ -function DillerDao(client) { +function DillerDao(tx) { var deviceColumns = 'id, key, created_timestamp'; var propertyColumns = 'id, device, key, created_timestamp'; + var valueColumns = 'property, timestamp, value'; + + // ------------------------------------------------------------------------------------------------------------------- + // Device + // ------------------------------------------------------------------------------------------------------------------- + + function devices() { + return tx.many("SELECT " + deviceColumns + " FROM device"); + } + + function deviceById(id) { + return tx.oneOrNone("SELECT " + deviceColumns + " FROM device WHERE id=$1", id); + } function deviceByKey(key) { - return client.oneOrNone("SELECT " + deviceColumns + " FROM device WHERE key=$1", key); + return tx.oneOrNone("SELECT " + deviceColumns + " FROM device WHERE key=$1", key); } function insertDevice(key) { - return client.one("INSERT INTO device(id, key, created_timestamp) VALUES(DEFAULT, $1, CURRENT_TIMESTAMP) RETURNING " + deviceColumns, key); + return tx.one("INSERT INTO device(id, key, created_timestamp) VALUES(DEFAULT, $1, CURRENT_TIMESTAMP) RETURNING " + deviceColumns, key); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Device Property + // ------------------------------------------------------------------------------------------------------------------- + + function devicePropertyById(id) { + return tx.one('SELECT ' + propertyColumns + ' FROM device_property WHERE id=$1', [id]); } function devicePropertyByDeviceIdAndKey(deviceId, key) { - return client.oneOrNone('SELECT id FROM device_property WHERE device=$1 AND key=$2', [deviceId, key]); + return tx.oneOrNone('SELECT id FROM device_property WHERE device=$1 AND key=$2', [deviceId, key]); + } + + function devicePropertiesByDeviceId(deviceId) { + return tx.many('SELECT ' + propertyColumns + ' FROM device_property WHERE device=$1', [deviceId]); } function insertDeviceProperty(deviceId, key) { - return client.oneOrNone('INSERT INTO device_property(id, device, key, created_timestamp) VALUES(DEFAULT, $1, $2, CURRENT_TIMESTAMP) RETURNING ' + propertyColumns, [deviceId, key]); + return tx.oneOrNone('INSERT INTO device_property(id, device, key, created_timestamp) VALUES(DEFAULT, $1, $2, CURRENT_TIMESTAMP) RETURNING ' + propertyColumns, [deviceId, key]); } function updatePropertyName(id, name) { - return client.none('UPDATE device_property SET name=$1 WHERE id=$2', name, id); + return tx.none('UPDATE device_property SET name=$1 WHERE id=$2', name, id); } function updatePropertyDescription(id, description) { - return client.none('UPDATE device_property SET description=$1 WHERE id=$2', description, id); + return tx.none('UPDATE device_property SET description=$1 WHERE id=$2', description, id); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Value + // ------------------------------------------------------------------------------------------------------------------- + + function valuesByPropertyId(propertyId, limit) { + limit = limit || 10; + return tx.many('SELECT timestamp, value FROM value WHERE property=$1 LIMIT $2', [propertyId, limit]); } function insertValue(propertyId, value) { - return client.one('INSERT INTO value(property, timestamp, value) VALUES($1, CURRENT_TIMESTAMP, $2) RETURNING timestamp', [propertyId, value]); + return tx.one('INSERT INTO value(property, timestamp, value) VALUES($1, CURRENT_TIMESTAMP, $2) RETURNING timestamp', [propertyId, value]); } function updateHourAggregatesForProperty(propertyId, timestamp) { - return client.none('DELETE FROM value_by_hour WHERE property=$1 AND timestamp=DATE_TRUNC(\'hour\', $2::TIMESTAMPTZ)', [propertyId, timestamp]) + return tx.none('DELETE FROM value_by_hour WHERE property=$1 AND timestamp=DATE_TRUNC(\'hour\', $2::TIMESTAMPTZ)', [propertyId, timestamp]) .then(function() { - return client.one('INSERT INTO value_by_hour(property, timestamp, count, max, min, avg) ' + + return tx.one('INSERT INTO value_by_hour(property, timestamp, count, max, min, avg) ' + 'SELECT property, DATE_TRUNC(\'hour\', timestamp) AS timestamp, COUNT(value) 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) ' + 'GROUP BY property, DATE_TRUNC(\'hour\', timestamp) ' + @@ -44,9 +78,9 @@ function DillerDao(client) { } function updateMinuteAggregatesForProperty(propertyId, timestamp) { - return client.none('DELETE FROM value_by_minute WHERE property=$1 AND timestamp=DATE_TRUNC(\'minute\', $2::TIMESTAMPTZ)', [propertyId, timestamp]) + return tx.none('DELETE FROM value_by_minute WHERE property=$1 AND timestamp=DATE_TRUNC(\'minute\', $2::TIMESTAMPTZ)', [propertyId, timestamp]) .then(function() { - return client.one('INSERT INTO value_by_minute(property, timestamp, count, max, min, avg) ' + + return tx.one('INSERT INTO value_by_minute(property, timestamp, count, max, min, avg) ' + 'SELECT property, DATE_TRUNC(\'minute\', timestamp) AS timestamp, COUNT(value) 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(\'minute\', timestamp)=DATE_TRUNC(\'minute\', $2::TIMESTAMPTZ) ' + @@ -57,14 +91,19 @@ function DillerDao(client) { } return { + devices: devices, + deviceById: deviceById, deviceByKey: deviceByKey, insertDevice: insertDevice, + devicePropertyById: devicePropertyById, devicePropertyByDeviceIdAndKey: devicePropertyByDeviceIdAndKey, + devicePropertiesByDeviceId: devicePropertiesByDeviceId, insertDeviceProperty: insertDeviceProperty, updatePropertyName: updatePropertyName, updatePropertyDescription: updatePropertyDescription, + valuesByPropertyId: valuesByPropertyId, insertValue: insertValue, updateHourAggregatesForProperty: updateHourAggregatesForProperty, updateMinuteAggregatesForProperty: updateMinuteAggregatesForProperty diff --git a/src/DillerDb.js b/src/DillerDb.js new file mode 100644 index 0000000..c31dde7 --- /dev/null +++ b/src/DillerDb.js @@ -0,0 +1,29 @@ +var di = require('di'); +var DillerConfig = require('./DillerConfig'); + +var pgpOptions = { + //query: function (e) { + // console.log("Query:", e.query); + // if (e.ctx) { + // // this query is executing inside a task or transaction, + // if (e.ctx.isTX) { + // // this query is inside a transaction; + // } else { + // // this query is inside a task; + // } + // + // } + //} +}; + +var pgp = require('pg-promise')(pgpOptions); + +function DillerDb(config) { + + return function () { + return pgp(config.postgresqlConfig) + } +} +di.annotate(DillerDb, new di.Inject(DillerConfig)); + +module.exports = DillerDb; diff --git a/src/config.js b/src/config.js deleted file mode 100644 index a42ec87..0000000 --- a/src/config.js +++ /dev/null @@ -1,18 +0,0 @@ -function isProd() { - return process.env.NODE_ENV == 'prod'; -} - -var mqttUrl = process.env.MQTT_URL || 'mqtt://trygvis.io'; - -var postgresqlConfig = { - host: process.env.DB_HOST || '/var/run/postgresql', - database: process.env.DB_DATABASE || 'diller', - user: process.env.DB_USER || 'diller', - password: process.env.DB_PASSWORD || 'diller' -}; - -module.exports = { - isProd: isProd, - mqttUrl: mqttUrl, - postgresqlConfig: postgresqlConfig -}; diff --git a/src/mqtt/DillerMqtt.js b/src/mqtt/DillerMqtt.js new file mode 100644 index 0000000..3fe43dc --- /dev/null +++ b/src/mqtt/DillerMqtt.js @@ -0,0 +1,38 @@ +var di = require('di'); +var mqtt = require('mqtt'); + +function DillerMqtt(config, diller) { + var log = config.log(); + + function run() { + log.info('Connecting to ' + config.mqttUrl); + var mqttClient = mqtt.connect(config.mqttUrl); + + mqttClient.on('offline', function () { + log.info('offline'); + }); + + mqttClient.on('error', function (error) { + log.info('error', {error: error}); + }); + + mqttClient.on('connect', function () { + log.info('Connected'); + mqttClient.subscribe('/diller/#'); + }); + + mqttClient.on('message', function (topic, message, payload) { + diller.onMessage(topic, message, payload); + }); + } + + return { + run: run + }; +} +var Diller = require('../Diller'); +var DillerConfig = require('../DillerConfig'); + +di.annotate(DillerMqtt, new di.Inject(DillerConfig, Diller)); + +module.exports = DillerMqtt; diff --git a/src/web/DillerWeb.js b/src/web/DillerWeb.js new file mode 100644 index 0000000..40fbc3d --- /dev/null +++ b/src/web/DillerWeb.js @@ -0,0 +1,158 @@ +var express = require('express'); +var bodyParser = require('body-parser'); +var _ = require('lodash'); +var di = require('di'); + +var DillerConfig = require('../DillerConfig'); +var Diller = require('../Diller'); +var DillerDb = require('../DillerDb'); +var DillerDao = require('../DillerDao'); + +function DillerWeb(diller, db, config) { + var log = config.log(); + + var calls = []; + var app; + + 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'}); + }); + } + + function getDevice(req, res) { + db().tx(function (tx) { + var deviceId = req.params.deviceId; + + var dao = new DillerDao(tx); + return tx.batch([ + dao.deviceById(deviceId), + dao.devicePropertiesByDeviceId(deviceId)] + ); + }).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'}); + }); + } + + function getValues(req, res) { + db().tx(function (tx) { + var propertyId = req.params.propertyId; + + var dao = new DillerDao(tx); + return dao.valuesByPropertyId(propertyId, 10); + }).then(function (values) { + res.json({values: values}); + }, function (err) { + log.warn('fail', err); + res.status(500).json({message: 'fail'}); + }); + } + + function init() { + app = express(); + + app.use(bodyParser.urlencoded({extended: true})); + app.use(bodyParser.json()); + + var router = express.Router(); + + function addRoute(name, method, path, callback) { + router[method](path, callback); + var layer = _.last(router.stack); + + calls.push({ + name: name, + method: method, + path: path, + layer: layer, + keys: _.map(layer.keys, function (key) { + return key.name; + }) + }); + } + + addRoute('getDevices', 'get', '/device', getDevices); + addRoute('getDevice', 'get', '/device/:deviceId', getDevice); + addRoute('getValues', 'get', '/property/:propertyId/values', getValues); + + app.use('/api', router); + app.use(express.static('web')); + } + + function listen() { + var port = process.env.HTTP_PORT || 8080; + app.listen(port); + } + + function generateRpc() { + console.log('function DillerRpc($http) {'); + + var 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' + + ' req.url = \'/api' + call.path + '\';\n'; + + s += _.map(call.layer.keys, function (key) { + return ' req.url = req.url.replace(/:' + key.name + '/, ' + key.name + ');\n' + }).join(''); + + s += + ' return $http(req);\n' + + ' }\n'; + + return s; + }); + _.each(s, function (x) { + console.log(x); + }); + + console.log(' return {'); + console.log(_.map(calls, function (call) { + return ' ' + call.name + ': ' + call.name + }).join(',\n')); + console.log(' };'); + console.log('}'); + console.log(''); + + console.log('DillerRpcResolve = {};'); + _.each(calls, function (call) { + var args = ['DillerRpc']; + + if (call.keys.length > 0) { + args.push('$route'); + } + console.log('DillerRpcResolve.' + call.name + ' = function(' + args.join(', ') + ') {'); + + args = _.map(call.keys, function (key) { + return '$route.current.params.' + key; + }); + console.log(' return DillerRpc.' + call.name + '(' + args.join(', ') + ');'); + console.log('};'); + }); + } + + return { + init: init, + listen: listen, + generateRpc: generateRpc + } +} + +di.annotate(DillerWeb, new di.Inject(Diller, DillerDb, DillerConfig)); + +module.exports = DillerWeb; diff --git a/web/app/DillerRpc.js b/web/app/DillerRpc.js new file mode 100644 index 0000000..a0c20fb --- /dev/null +++ b/web/app/DillerRpc.js @@ -0,0 +1,41 @@ +function DillerRpc($http) { + function getDevices() { + var req = {}; + req.method = 'get'; + req.url = '/api/device'; + return $http(req); + } + + function getDevice(deviceId) { + var req = {}; + req.method = 'get'; + req.url = '/api/device/:deviceId'; + req.url = req.url.replace(/:deviceId/, deviceId); + return $http(req); + } + + function getValues(propertyId) { + var req = {}; + req.method = 'get'; + req.url = '/api/property/:propertyId/values'; + req.url = req.url.replace(/:propertyId/, propertyId); + return $http(req); + } + + return { + getDevices: getDevices, + getDevice: getDevice, + getValues: getValues + }; +} + +DillerRpcResolve = {}; +DillerRpcResolve.getDevices = function(DillerRpc) { + return DillerRpc.getDevices(); +}; +DillerRpcResolve.getDevice = function(DillerRpc, $route) { + return DillerRpc.getDevice($route.current.params.deviceId); +}; +DillerRpcResolve.getValues = function(DillerRpc, $route) { + return DillerRpc.getValues($route.current.params.propertyId); +}; diff --git a/web/app/app.js b/web/app/app.js new file mode 100644 index 0000000..cd14ae5 --- /dev/null +++ b/web/app/app.js @@ -0,0 +1,60 @@ +(function () { + function FrontPageController(devices) { + var ctrl = this; + + ctrl.devices = devices.data.devices; + } + + function DeviceController(device) { + var ctrl = this; + + ctrl.device = device.data.device; + } + + function PropertyController($route, device, values) { + var ctrl = this; + + ctrl.device = device.data.device; + ctrl.property = _.find(ctrl.device.properties, {id: $route.current.params.propertyId}); + ctrl.values = values.data.values; + } + + function config($routeProvider, $locationProvider) { + $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: '/' + }); + + //$locationProvider.html5Mode(true); + } + + angular + .module('Diller', ['ngRoute']) + .config(config) + .service('DillerRpc', DillerRpc); +})(); diff --git a/web/app/templates/device.html b/web/app/templates/device.html new file mode 100644 index 0000000..ae028b5 --- /dev/null +++ b/web/app/templates/device.html @@ -0,0 +1,21 @@ +<div class="container"> + + <h1> + {{ctrl.device.key}} + <small class="text-muted">device</small> + </h1> + + <ul> + <li>Created: {{ctrl.device.created_timestamp | date}}</li> + <li>Name: {{ctrl.property.name}}</li> + <li>Description: {{ctrl.property.description}}</li> + </ul> + + <h3>Properties</h3> + + <ul> + <li ng-repeat="p in ctrl.device.properties | orderBy:'key'"> + <a href="#/device/{{ctrl.device.id}}/property/{{p.id}}">{{p.key}}</a> + </li> + </ul> +</div> diff --git a/web/app/templates/front-page.html b/web/app/templates/front-page.html new file mode 100644 index 0000000..1846dea --- /dev/null +++ b/web/app/templates/front-page.html @@ -0,0 +1,24 @@ +<div class="container"> + + <h1> + Diller + <small class="text-muted">All your sensor data are belong to us</small> + </h1> + + <h2>Devices</h2> + + <table> + <thead> + <tr> + <th>Key</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="d in ctrl.devices | orderBy:'key'"> + <td> + <a href="#/device/{{d.id}}">{{d.key}}</a> + </td> + </tr> + </tbody> + </table> +</div> diff --git a/web/app/templates/property.html b/web/app/templates/property.html new file mode 100644 index 0000000..65a66e8 --- /dev/null +++ b/web/app/templates/property.html @@ -0,0 +1,36 @@ +<div class="container"> + + <h1> + <a href="#/device/{{ctrl.device.id}}">{{ctrl.device.key}}</a> + <small class="muted">device</small> + </h1> + + <h2> + {{ctrl.property.key}} + <small class="muted">property</small> + </h2> + + <ul> + <li>Created: {{ctrl.property.created_timestamp | date}}</li> + <li>Name: {{ctrl.property.name}}</li> + <li>Description: {{ctrl.property.description}}</li> + </ul> + + <h3>Latest Values</h3> + + <table class="table"> + <thead> + <tr> + <th>Timestamp</th> + <th>Value</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="v in ctrl.values"> + <td>{{v.timestamp | date:'medium'}}</td> + <td>{{v.value}}</td> + </tr> + </tbody> + </table> + +</div> diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..b591e55 --- /dev/null +++ b/web/index.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta http-equiv="x-ua-compatible" content="ie=edge"> + + <script src="bower_components/jquery/dist/jquery.js" type="application/javascript"></script> + + <script src="bower_components/bootstrap/dist/js/bootstrap.js" type="application/javascript"></script> + <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"/> + + <script src="bower_components/angular/angular.js" type="application/javascript"></script> + <script src="bower_components/angular-route/angular-route.js" type="application/javascript"></script> + <script src="bower_components/lodash/lodash.js" type="application/javascript"></script> + + <script src="app/DillerRpc.js" type="application/javascript"></script> + <script src="app/app.js" type="application/javascript"></script> + <base href="/"> +</head> +<body ng-app="Diller" ng-view> +Loading Diller ... +</body> +</html> |