diff --git a/README.md b/README.md index 88e3cab99b4..fff1d1180a6 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,13 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `BRIDGE_FIRST_FETCH_COUNT` (`3`) - Changes max count during the very first update only. * `BRIDGE_MAX_FAILURES` (`3`) - How many failures before giving up. * `BRIDGE_MINUTES` (`1400`) - The time window to search for new data per update (default is one day in minutes). + * `mmconnect` (MiniMed Connect bridge) - Transfer real-time MiniMed Connect data from the Medtronic CareLink server into Nightscout ([read more](https://github.com/mddub/minimed-connect-to-nightscout)) + * `MMCONNECT_USER_NAME` - Your user name for CareLink Connect. + * `MMCONNECT_PASSWORD` - Your password for CareLink Connect. + * `MMCONNECT_INTERVAL` (`60000` *1 minute*) - Number of milliseconds to wait between requests to the CareLink server. + * `MMCONNECT_MAX_RETRY_DURATION` (`32`) - Maximum number of total seconds to spend retrying failed requests before giving up. + * `MMCONNECT_SGV_LIMIT` (`24`) - Maximum number of recent sensor glucose values to send to Nightscout on each request. + * `MMCONNECT_VERBOSE` - Set this to any truthy value to log CareLink request information to the console. Also see [Pushover](#pushover) and [IFTTT Maker](#ifttt-maker). diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 0ca7d7235eb..d78ecb3c868 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -10,6 +10,7 @@ , units: require('../lib/units')() , plugins: require('../lib/plugins/')().registerClientDefaults() , report_plugins: require('../lib/report_plugins/')() + , admin_plugins: require('../lib/admin_plugins/')() }; console.info('Nightscout bundle ready'); diff --git a/lib/admin_plugins/cleanstatusdb.js b/lib/admin_plugins/cleanstatusdb.js new file mode 100644 index 00000000000..86e61e987f8 --- /dev/null +++ b/lib/admin_plugins/cleanstatusdb.js @@ -0,0 +1,70 @@ +'use strict'; + +var cleanstatusdb = { + name: 'cleanstatusdb' + , label: 'Clean Mongo status database' + , pluginType: 'admin' +}; + +function init() { + return cleanstatusdb; +} + +module.exports = init; + +cleanstatusdb.actions = [ + { + name: 'Delete all documents from devicestatus collection' + , description: 'This task removes all documents from devicestatus collection. Useful when uploader battery status is not properly updated.' + , buttonLabel: 'Delete all documents' + , confirmText: 'Delete all documents from devicestatus collection?' + } + ]; + +cleanstatusdb.actions[0].init = function init(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + cleanstatusdb.name + '_0_status'); + + $status.hide().text(translate('Loading database ...')).fadeIn('slow'); + $.ajax('/api/v1/devicestatus.json?count=500', { + success: function (records) { + var recs = (records.length === 500 ? '500+' : records.length); + $status.hide().text(translate('Database contains %1 records',{ params: [recs] })).fadeIn('slow'); + }, + error: function () { + $status.hide().text(translate('Error loading database')).fadeIn('slow'); + } + }).done(function () { if (callback) { callback(); } }); +}; + +cleanstatusdb.actions[0].code = function deleteRecords(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + cleanstatusdb.name + '_0_status'); + + if (!client.hashauth.isAuthenticated()) { + alert(translate('Your device is not authenticated yet')); + if (callback) { + callback(); + } + return; + }; + + $status.hide().text(translate('Deleting records ...')).fadeIn('slow'); + $.ajax({ + method: 'DELETE' + , url: '/api/v1/devicestatus/*' + , headers: { + 'api-secret': client.hashauth.hash() + } + }).done(function success () { + $status.hide().text(translate('All records removed ...')).fadeIn('slow'); + if (callback) { + callback(); + } + }).fail(function fail() { + $status.hide().text(translate('Error')).fadeIn('slow'); + if (callback) { + callback(); + } + }); +}; diff --git a/lib/admin_plugins/futureitems.js b/lib/admin_plugins/futureitems.js new file mode 100644 index 00000000000..ab888aff0a6 --- /dev/null +++ b/lib/admin_plugins/futureitems.js @@ -0,0 +1,172 @@ +'use strict'; + +var futureitems = { + name: 'futureitems' + , label: 'Remove future items from mongo database' + , pluginType: 'admin' +}; + +function init() { + return futureitems; +} + +module.exports = init; + +futureitems.actions = [ + { + name: 'Find and remove treatments in the future' + , description: 'This task find and remove treatments in the future.' + , buttonLabel: 'Remove treatments in the future' + } + , { + name: 'Find and remove entries in the future' + , description: 'This task find and remove CGM data in the future created by uploader with wrong date/time.' + , buttonLabel: 'Remove entries in the future' + } + ]; + +futureitems.actions[0].init = function init(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + futureitems.name + '_0_status'); + + function valueOrEmpty (value) { + return value ? value : ''; + } + + function showOneTreatment (tr, table) { + table.append($('').css('background-color','#0f0f0f') + .append($('').attr('width','20%').append(new Date(tr.created_at).toLocaleString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'))) + .append($('').attr('width','20%').append(tr.eventType ? translate(client.careportal.resolveEventName(tr.eventType)) : '')) + .append($('').attr('width','10%').attr('align','center').append(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')) + .append($('').attr('width','10%').attr('align','center').append(valueOrEmpty(tr.insulin))) + .append($('').attr('width','10%').attr('align','center').append(valueOrEmpty(tr.carbs))) + .append($('').attr('width','10%').append(valueOrEmpty(tr.enteredBy))) + .append($('').attr('width','20%').append(valueOrEmpty(tr.notes))) + ); + } + + function showTreatments(treatments, table) { + table.append($('').css('background','#040404') + .append($('').css('width','80px').attr('align','left').append(translate('Time'))) + .append($('').css('width','150px').attr('align','left').append(translate('Event Type'))) + .append($('').css('width','150px').attr('align','left').append(translate('Blood Glucose'))) + .append($('').css('width','50px').attr('align','left').append(translate('Insulin'))) + .append($('').css('width','50px').attr('align','left').append(translate('Carbs'))) + .append($('').css('width','150px').attr('align','left').append(translate('Entered By'))) + .append($('').css('width','300px').attr('align','left').append(translate('Notes'))) + ); + for (var t=0; t').css('margin-top','10px'); + $('#admin_' + futureitems.name + '_0_html').append(table); + showTreatments(records, table); + futureitems.actions[0].confirmText = translate('Remove %1 selected records?', { params: [records.length] }); + }, + error: function () { + $status.hide().text(translate('Error loading database')).fadeIn('slow'); + futureitems.treatmentrecords = []; + } + }).done(function () { if (callback) { callback(); } }); +}; + +futureitems.actions[0].code = function deleteRecords(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + futureitems.name + '_0_status'); + + if (!client.hashauth.isAuthenticated()) { + alert(translate('Your device is not authenticated yet')); + if (callback) { + callback(); + } + return; + }; + + function deleteRecordById (_id) { + $.ajax({ + method: 'DELETE' + , url: '/api/v1/treatments/' + _id + , headers: { + 'api-secret': client.hashauth.hash() + } + }).done(function success () { + $status.text(translate('Record %1 removed ...', { params: [_id] })); + }).fail(function fail() { + $status.text(translate('Error removing record %1', { params: [_id] })); + }); + } + + $status.hide().text(translate('Deleting records ...')).fadeIn('slow'); + for (var i = 0; i < futureitems.treatmentrecords.length; i++) { + deleteRecordById(futureitems.treatmentrecords[i]._id); + } + $('#admin_' + futureitems.name + '_0_html').html(''); + + if (callback) { + callback(); + } +}; + +futureitems.actions[1].init = function init(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + futureitems.name + '_1_status'); + + $status.hide().text(translate('Loading database ...')).fadeIn('slow'); + var now = new Date().getTime(); + $.ajax('/api/v1/entries.json?&find[date][$gte]=' + now, { + success: function (records) { + futureitems.entriesrecords = records; + $status.hide().text(translate('Database contains %1 future records',{ params: [records.length] })).fadeIn('slow'); + futureitems.actions[1].confirmText = translate('Remove %1 selected records?', { params: [records.length] }); + }, + error: function () { + $status.hide().text(translate('Error loading database')).fadeIn('slow'); + futureitems.entriesrecords = []; + } + }).done(function () { if (callback) { callback(); } }); +}; + +futureitems.actions[1].code = function deleteRecords(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + futureitems.name + '_1_status'); + + if (!client.hashauth.isAuthenticated()) { + alert(translate('Your device is not authenticated yet')); + if (callback) { + callback(); + } + return; + }; + + function deteleteRecordById (_id) { + $.ajax({ + method: 'DELETE' + , url: '/api/v1/entries/' + _id + , headers: { + 'api-secret': client.hashauth.hash() + } + }).done(function success () { + $status.text(translate('Record %1 removed ...', { params: [_id] })); + }).fail(function fail() { + $status.text(translate('Error removing record %1', { params: [_id] })); + }); + } + + + $status.hide().text(translate('Deleting records ...')).fadeIn('slow'); + for (var i = 0; i < futureitems.entriesrecords.length; i++) { + deteleteRecordById(futureitems.entriesrecords[i]._id); + } + + if (callback) { + callback(); + } +}; diff --git a/lib/admin_plugins/index.js b/lib/admin_plugins/index.js new file mode 100644 index 00000000000..f7a5c74133d --- /dev/null +++ b/lib/admin_plugins/index.js @@ -0,0 +1,79 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + var allPlugins = [ + require('./cleanstatusdb')() + , require('./futureitems')() + ]; + + function plugins(name) { + if (name) { + return _.find(allPlugins, {name: name}); + } else { + return plugins; + } + } + + plugins.eachPlugin = function eachPlugin(f) { + _.each(allPlugins, f); + }; + + plugins.createHTML = function createHTML(client) { + var translate = client.translate; + plugins.eachPlugin(function addHtml(p) { + var fs = $('
'); + $('#admin_placeholder').append(fs); + fs.append($('').append(translate(p.label))); + for (var i = 0; i < p.actions.length; i++) { + if (i !== 0) { + fs.append('
'); + } + var a = p.actions[i]; + // add main plugin html + fs.append($('').css('text-decoration','underline').append(translate(a.name))); + fs.append('
'); + fs.append($('').append(translate(a.description))); + fs.append($('
').attr('id','admin_' + p.name + '_' + i + '_html')); + fs.append($('