diff --git a/README.md b/README.md index 8c45a3a6858..e2e312c1b97 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ cgm-remote-monitor (a.k.a. NightScout) ====================================== - + [![Build Status](https://travis-ci.org/nightscout/cgm-remote-monitor.png)](https://travis-ci.org/nightscout/cgm-remote-monitor) [![Dependency Status](https://david-dm.org/nightscout/cgm-remote-monitor.png)](https://david-dm.org/nightscout/cgm-remote-monitor) [![Gitter chat](https://badges.gitter.im/nightscout.png)](https://gitter.im/nightscout/public) diff --git a/bower.json b/bower.json index 634093da1f6..5461e62435a 100644 --- a/bower.json +++ b/bower.json @@ -3,6 +3,12 @@ "version": "0.0.1", "dependencies": { "d3": "3.4.3", + "jquery": "2.1.0", + "jQuery-Storage-API": "~1.7.2", + "tipsy-jmalonzo": "~1.0.1", + "jsSHA": "~1.5.0" + }, + "resolutions": { "jquery": "2.1.0" } } diff --git a/deploy.sh b/deploy.sh index 91801faae9e..4bc630de4ab 100755 --- a/deploy.sh +++ b/deploy.sh @@ -2,7 +2,7 @@ # ---------------------- # KUDU Deployment Script -# Version: 0.1.10 +# Version: 0.1.11 # ---------------------- # Helpers @@ -100,32 +100,22 @@ selectNodeVersion () { echo Handling node.js deployment. -# 1. Select node version +# 1. KuduSync +if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then + "$KUDU_SYNC_CMD" -v 50 -f "$DEPLOYMENT_SOURCE" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh" + exitWithMessageOnError "Kudu Sync failed" +fi + +# 2. Select node version selectNodeVersion -# 2. Install npm packages -if [ -e "$DEPLOYMENT_SOURCE/package.json" ]; then - # cd "$DEPLOYMENT_SOURCE" - eval $NPM_CMD set ca "" +# 3. Install npm packages +if [ -e "$DEPLOYMENT_TARGET/package.json" ]; then + cd "$DEPLOYMENT_TARGET" eval $NPM_CMD install --production exitWithMessageOnError "npm failed" - # cd - > /dev/null + cd - > /dev/null fi -if [ -e "$DEPLOYMENT_SOURCE/bower.json" ]; then - # cd "$DEPLOYMENT_SOURCE" - ./node_modules/.bin/bower install - exitWithMessageOnError "bower failed" - # cd - > /dev/null -fi - -# 3. KuduSync -# if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then -# "$KUDU_SYNC_CMD" -v 50 -f "$DEPLOYMENT_SOURCE" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh" -# exitWithMessageOnError "Kudu Sync failed" -# fi -# 3. KuduSync to Target -"$KUDU_SYNC_CMD" -v 500 -f "$DEPLOYMENT_SOURCE/" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh" -exitWithMessageOnError "Kudu Sync to Target failed" ################################################################################################################################## diff --git a/env.js b/env.js index cb2419c3316..1ec8ab1d334 100644 --- a/env.js +++ b/env.js @@ -17,7 +17,11 @@ function config ( ) { * static files over http. Default value is the included `static` * directory. */ + var software = require('./package.json'); + env.version = software.version; + env.name = software.name; + env.DISPLAY_UNITS = process.env.DISPLAY_UNITS || 'mg/dl'; env.PORT = process.env.PORT || 1337; env.mongo = process.env.MONGO_CONNECTION || process.env.CUSTOMCONNSTR_mongo; env.mongo_collection = process.env.CUSTOMCONNSTR_mongo_collection || 'entries'; diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 69d1f3c22b6..42e07854e1e 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -104,9 +104,10 @@ function configure (app, wares, entries) { es.pipeline(inputs( ), persist(done)); } - api.get('/', function(req, res, next) { + api.get('/entries', function(req, res, next) { // If "?count=" is present, use that number to decided how many to return. var query = req.query; + if (!query.count) { query.count = 10 }; entries.list(query, function(err, entries) { res.entries = entries; res.entries_err = err; @@ -115,7 +116,7 @@ function configure (app, wares, entries) { return; }, format_entries); - api.get('/current', function(req, res, next) { + api.get('/entries/current', function(req, res, next) { entries.list({count: 1}, function(err, records) { res.entries = records; res.entries_err = err; @@ -127,7 +128,7 @@ function configure (app, wares, entries) { // Allow previewing your post content, just echos everything you // posted back out. - api.post('/preview', function (req, res, next) { + api.post('/entries/preview', function (req, res, next) { req.persist_entries = false; next( ); return; @@ -135,7 +136,7 @@ function configure (app, wares, entries) { if (app.enabled('api')) { // Create and store new sgv entries - api.post('/', wares.verifyAuthorization, function (req, res, next) { + api.post('/entries/', wares.verifyAuthorization, function (req, res, next) { req.persist_entries = true; next( ); return; @@ -143,7 +144,7 @@ function configure (app, wares, entries) { } // Fetch one entry by id - api.get('/find/:id', function(req, res) { + api.get('/entries/:id', function(req, res) { entries.getEntry(function(err, entry) { if (err) res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); diff --git a/lib/api/index.js b/lib/api/index.js index 29c69115a0f..7fce2bbe2a9 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -8,6 +8,9 @@ function create (env, entries, settings) { var wares = require('../middleware/')(env); // set up express app with our options + app.set('name', env.name); + app.set('version', env.version); + app.set('units', env.DISPLAY_UNITS); // Only allow access to the API if API_SECRET is set on the server. app.disable('api'); if (env.api_secret) { @@ -15,7 +18,7 @@ function create (env, entries, settings) { app.enable('api'); } - app.set('title', 'Nightscout API v1'); + app.set('title', [app.get('name'), 'API', app.get('version')].join(' ')); // Start setting up routes if (app.enabled('api')) { @@ -24,11 +27,11 @@ function create (env, entries, settings) { } // Entries and settings - app.use('/entries', require('./entries/')(app, wares, entries)); - app.use('/settings', require('./settings/')(app, wares, settings)); + app.use('/', require('./entries/')(app, wares, entries)); + app.use('/', require('./settings/')(app, wares, settings)); // Status - app.use('/status', require('./status')(app, wares)); + app.use('/', require('./status')(app, wares)); return app; } diff --git a/lib/api/settings/index.js b/lib/api/settings/index.js index 815fecec0f2..c997024270a 100644 --- a/lib/api/settings/index.js +++ b/lib/api/settings/index.js @@ -19,44 +19,47 @@ function configure (app, wares, settings) { /**********\ * Settings \**********/ + // Handler for grabbing alias/profile + api.param('alias', function (req, res, next, alias) { + settings.alias(alias, function (err, profile) { + req.alias = profile; + next(err); + }); + }); + + // List settings available + api.get('/settings/', function(req, res) { + settings.list(function (err, profiles) { + return res.json(profiles); + }); + }); + // Fetch settings - api.get('/', function(req, res) { - settings.getSettings(function(err, settings) { - if (err) - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - else - return res.json(settings); - }); + api.get('/settings/:alias', function(req, res) { + res.json(req.alias); }); function config_authed (app, api, wares, settings) { // Delete settings - api.delete('/', wares.verifyAuthorization, function(req, res) { - settings.remove(function ( ) { + api.delete('/settings/:alias', wares.verifyAuthorization, function(req, res) { + settings.remove(req.alias.alias, function ( ) { res.json({ }); }); }); - // Replace settings - api.put('/', wares.verifyAuthorization, function(req, res) { - // Retrieve the JSON formatted record. - var json = req.body; + function save_settings (req, res) { + var b = req.body; + b.alias = req.params.alias + settings.update(b, function (err, profile) { + res.json(profile); + }); + } + + // Update settings + api.put('/settings/:alias', wares.verifyAuthorization, save_settings); + api.post('/settings/:alias', wares.verifyAuthorization, save_settings); - // Send the new settings to mongodb. - settings.updateSettings(json, function(err, config) { - if (err) - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - else { - // Add a warning to the outgoing status when HTTPS is not being used. - var warning = ''; - if (req.secure === false) - warning = 'WARNING: HTTPS is required to secure your data!'; - - res.json(config); - } - }); - }); } if (app.enabled('api')) { diff --git a/lib/api/status.js b/lib/api/status.js index 4be93c5ca09..6e524f5cf1f 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -6,11 +6,15 @@ function configure (app, wares) { ; api.use(wares.extensions([ - 'json', 'svg', 'csv', 'txt', 'png', 'html' + 'json', 'svg', 'csv', 'txt', 'png', 'html', 'js' ])); // Status badge/text/json - api.get('/', function (req, res, next) { - var status = {status: 'ok'}; + api.get('/status', function (req, res, next) { + var info = { status: 'ok' + , apiEnabled: app.enabled('api') + , units: app.get('units') + , version: app.get('version') + , name: app.get('name')}; var badge = 'http://img.shields.io/badge/Nightscout-OK-green'; return res.format({ html: function ( ) { @@ -22,11 +26,17 @@ function configure (app, wares) { svg: function ( ) { res.redirect(302, badge + '.svg'); }, + js: function ( ) { + var head = "this.serverSettings ="; + var body = JSON.stringify(info); + var tail = ';'; + res.send([head, body, tail].join(' ')); + }, text: function ( ) { res.send("STATUS OK"); }, json: function ( ) { - res.json(status); + res.json(info); } }); }); diff --git a/lib/middleware/verify-token.js b/lib/middleware/verify-token.js index 0a390a0b5ea..f7f875fa83a 100644 --- a/lib/middleware/verify-token.js +++ b/lib/middleware/verify-token.js @@ -5,12 +5,12 @@ function configure (env) { function verifyAuthorization(req, res, next) { // Retrieve the secret values to be compared. var api_secret = env.api_secret; - var secret = req.params.secret ? req.params.secret : req.header('API_SECRET'); + var secret = req.params.secret ? req.params.secret : req.header('api-secret'); // Return an error message if the authorization fails. var unauthorized = (typeof api_secret === 'undefined' || secret != api_secret); if (unauthorized) { - res.sendJSONStatus(res, consts.HTTP_UNAUTHORIZED, 'Unauthorized', 'API_SECRET Request Header is incorect or missing.'); + res.sendJSONStatus(res, consts.HTTP_UNAUTHORIZED, 'Unauthorized', 'api-secret Request Header is incorrect or missing.'); } else { next(); } diff --git a/lib/pebble.js b/lib/pebble.js index a763b23760c..2eaef9c7379 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -21,35 +21,54 @@ function directionToTrend (direction) { function pebble (req, res) { var FORTY_MINUTES = 2400000; - var cgmData = [ ]; + + function requestMetric() { + var units = req.query.units; + if (units == "mmol") { + return true; + } + return false; + } + var useMetricBg = requestMetric(); + + function scaleBg(bg) { + if (useMetricBg) { + return (Math.round((bg / 18) * 10) / 10).toFixed(1); + } else + return bg; + } + function get_latest (err, results) { var now = Date.now(); - results.forEach(function(element, index, array) { - var next = null; - if (index + 1 < results.length) { - next = results[index + 1]; + results.forEach(function(element, index, array) { + var next = null; + if (index + 1 < results.length) { + next = results[index + 1]; + } + if (element) { + var obj = {}; + obj.sgv = scaleBg(element.sgv); + obj.bgdelta = (next ? (scaleBg(element.sgv) - scaleBg(next.sgv) ) : 0); + if (useMetricBg) { + obj.bgdelta = obj.bgdelta.toFixed(1); } - if (element) { - var obj = {}; - obj.sgv = element.sgv; - obj.bgdelta = (next ? (element.sgv - next.sgv ) : 0); - if ('direction' in element) { - obj.trend = directionToTrend(element.direction); - obj.direction = element.direction; - } - // obj.y = element.sgv; - // obj.x = element.date; - obj.datetime = element.date; - // obj.date = element.date.toString( ); - cgmData.push(obj); + if ('direction' in element) { + obj.trend = directionToTrend(element.direction); + obj.direction = element.direction; } - }); - var result = { status: [ {now:now}], bgs: cgmData.slice(0, 1) }; - res.setHeader('content-type', 'application/json'); - res.write(JSON.stringify(result)); - res.end( ); - // collection.db.close(); + // obj.y = element.sgv; + // obj.x = element.date; + obj.datetime = element.date; + // obj.date = element.date.toString( ); + cgmData.push(obj); + } + }); + var result = { status: [ {now:now}], bgs: cgmData.slice(0, 1) }; + res.setHeader('content-type', 'application/json'); + res.write(JSON.stringify(result)); + res.end( ); + // collection.db.close(); } req.entries.list({count: 2}, get_latest); } @@ -63,4 +82,3 @@ function configure (entries) { configure.pebble = pebble; module.exports = configure; - diff --git a/lib/settings.js b/lib/settings.js index c094d6bf704..e241078082e 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -3,10 +3,50 @@ var utils = require('./utils'); var ObjectID = require('mongodb').ObjectID; +function defaults ( ) { + var DEFAULT_SETTINGS_JSON = { + "units": "mg/dl" + }; // possible future settings: "theme": "subdued", "websockets": false, alertLow: 80, alertHigh: 180 + return DEFAULT_SETTINGS_JSON; +} + function configure (collection, storage) { var DEFAULT_SETTINGS_JSON = { "units": "mg/dl" - }; // possible future settings: "theme": "subdued", "websockets": false + }; + + function pop (fn) { + return function (err, results) { + if (err) fn(err); + fn(err, results.pop( )); + } + } + + function alias (alias, fn) { + return api( ).find({ alias: alias }).toArray(pop(fn)); + } + + function create (obj, fn) { + var result = merge(DEFAULT_SETTINGS_JSON, obj); + result.alias = obj.alias; + result.created_at = (new Date( )).toISOString( ); + api( ).insert(result, function (err, doc) { + fn(null, doc); + }); + } + + function lint (obj) { + var result = merge(DEFAULT_SETTINGS_JSON, json); + if (result.alias) return result; + } + + function list (fn) { + return api( ).find({ }, { alias: 1, nick: 1 }).toArray(pop(fn)); + } + + function remove (alias, fn) { + return api( ).remove({ alias: alias }, fn); + } var with_collection = storage.with_collection(collection); function getSettings (fn) { @@ -40,6 +80,34 @@ function configure (collection, storage) { return update; } + function update (json, fn) { + var updated = (new Date( )).toISOString( ); + alias(json.alias, function last (err, older) { + if (err) { return fn(err); } + var result; + if (older && older._id) { + // result = merge(older, json); + result = json; + result.updated_at = updated; + var deltas = Object.keys(result); + if (deltas.length < 1) { + fn("Bad Keys"); + return; + } + api( ).update( + { '_id' : new ObjectID(older._id) }, + { $set: result }, + function (err, res) { + // Return to the calling function to display our success. + fn(err, res); + } + ); + } else { + create(json, fn); + } + }); + } + function updateSettings (json, fn) { getSettings(function last (err, older) { var result = merge(older, json); @@ -73,19 +141,25 @@ function configure (collection, storage) { }); } - function remove (fn) { + function clear (fn) { with_collection(function (err, collection) { collection.remove({ }, fn); }); } function api ( ) { - storage.pool.db.collection(collection); + return storage.pool.db.collection(collection); } api.getSettings = getSettings; api.updateSettings = updateSettings; api.remove = remove; + api.clear = clear; + api.list = list; + api.create = create; + api.lint = lint; + api.alias = alias; + api.update = update; return api; } module.exports = configure; diff --git a/lib/websocket.js b/lib/websocket.js index 49dd8824db6..9ac00c2d7e8 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -142,7 +142,7 @@ function loadData() { actual.sort(function(a, b) { return a.x - b.x; }); - + // sgv less than or equal to 10 means error code // or warm up period code, so ignore actual = actual.filter(function (a) { @@ -195,6 +195,8 @@ function loadData() { avgLoss += 1 / size * Math.pow(log10(predicted[j].y / 120), 2); } + //console.log(alarms['urgent_alarm'].threshold); + //console.log(alarms['alarm'].threshold); if (avgLoss > alarms['urgent_alarm'].threshold) { emitAlarm('urgent_alarm'); } else if (avgLoss > alarms['alarm'].threshold) { @@ -242,4 +244,3 @@ function loadData() { function log10(val) { return Math.log(val) / Math.LN10; } module.exports = websocket; - diff --git a/package.json b/package.json index ffdaf1e394c..8bead2fed96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.1.20140708", + "version": "0.3.0", "description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.", "license": "MIT", "author": "Nightscout Team", diff --git a/server.js b/server.js index 8921d5f9c93..8d8e460fece 100644 --- a/server.js +++ b/server.js @@ -46,14 +46,16 @@ var appInfo = software.name + ' ' + software.version; app.set('title', appInfo); app.enable('trust proxy'); // Allows req.secure test on heroku https connections. -if (env.api_secret) { - console.log("API_SECRET", env.api_secret); -} +//if (env.api_secret) { +// console.log("API_SECRET", env.api_secret); +//} app.use('/api/v1', api); // pebble data app.get('/pebble', pebble(entries)); +//app.get('/package.json', software); + // define static server var staticFiles = express.static(env.static_files, {maxAge: THIRTY_DAYS * 1000}); diff --git a/static/clock.html b/static/clock.html new file mode 100644 index 00000000000..e28fc9f0ee2 --- /dev/null +++ b/static/clock.html @@ -0,0 +1,69 @@ + + +
+ +