diff --git a/README.md b/README.md index bd55ebf8094..d9e4a57262c 100644 --- a/README.md +++ b/README.md @@ -306,11 +306,29 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or ### Views - There are a few alternate web views available from the main menu that display a simplified BG stream. (If you launch one of these in a fullscreen view in iOS, you can use a left-to-right swipe gesture to exit the view.) + Nightscout allows to create custom, simplified views using a predefined set of elements. This option is available under `[+]` link in the main menu. + + List of available items: + * `SGV` - Sensor Glucose Value + * `SGV age` - time since the last SGV read + * `SGV delta` - change of SGV in the last 5 minutes + * `Trend arrow` - icon of the SG trend + * `Time` - current time + * `Line break` - invisible item that will move following items to the next line (by default all are showing on the same level) + + All visible items have `Size` property which allows to customize the view even more. Also, all items may appear multiple times on the view. + + Apart from adding items, it is possible to customize other aspects of the views, like selecting `Color` or `Black` background. The first one will indicate current BG threshold (green = in range; blue = below range; yellow = above range; red = urgent below/above). + `Show SGV age` option will make `SGV age` item appear `Always` or only if the predefined threshold is reached: `Only after threshold`. Breaching `SGV age threshold` will also make `Color` background turn grey and strike through `SGV`. + `Clock view configurator` will generate an URL (available under `Open my clock view!` link) that could be bookmarked. + + There are a few default views available from the main menu: * `Clock` - Shows current BG, trend arrow, and time of day. Grey text on a black background. - * `Color` - Shows current BG and trend arrow. White text on a background that changes color to indicate current BG threshold (green = in range; blue = below range; yellow = above range; red = urgent below/above). Set `SHOW_CLOCK_DELTA` to `true` to show BG change in the last 5 minutes, set `SHOW_CLOCK_LAST_TIME` to `true` to always show BG age. + * `Color` - Shows current BG and trend arrow. White text on a color background. * `Simple` - Shows current BG. Grey text on a black background. + If you launch one of these views in a fullscreen view in iOS, you can use a left-to-right swipe gesture to exit the view. + ### Split View Some users will need easy access to multiple Nightscout views at the same time. We have a special view for this case, accessed on /split path on your Nightscout URL. The view supports any number of sites between 1 to 8 way split, where the content for the screen can be loaded from multiple Nightscout instances. Note you still need to host separate instances for each Nightscout being monitored including the one that hosts the split view page - these variables only add the ability to load multiple views into one browser page. To set the URLs from which the content is loaded, set: @@ -557,6 +575,7 @@ For remote overrides, the following extended settings must be configured: Plugins only have access to their own extended settings, all the extended settings of client plugins will be sent to the browser. * `DEVICESTATUS_ADVANCED` (`true`) - Defaults to true. Users who only have a single device uploading data to Nightscout can set this to false to reduce the data use of the site. + * `DEVICESTATUS_DAYS` (`1`) - Defaults to 1, can optionally be set to 2. Users can use this to show 48 hours of device status data for in retro mode, rather than the default 24 hours. Setting this value to 2 will roughly double the bandwidth usage of nightscout, so users with a data cap may not want to update this setting. #### Pushover In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. diff --git a/env.js b/env.js index 0d8d41409b0..9875435ff02 100644 --- a/env.js +++ b/env.js @@ -170,6 +170,8 @@ function findExtendedSettings (envs) { extended.devicestatus = {}; extended.devicestatus.advanced = true; + extended.devicestatus.days = 1; + if(process.env['DEVICESTATUS_DAYS'] && process.env['DEVICESTATUS_DAYS'] == '2') extended.devicestatus.days = 1; function normalizeEnv (key) { return key.toUpperCase().replace('CUSTOMCONNSTR_', ''); diff --git a/lib/api/profile/index.js b/lib/api/profile/index.js index 30f8fe240ca..faae33518ec 100644 --- a/lib/api/profile/index.js +++ b/lib/api/profile/index.js @@ -16,6 +16,31 @@ function configure (app, wares, ctx) { api.use(wares.bodyParser.urlencoded({ extended: true })); api.use(ctx.authorization.isPermitted('api:profile:read')); + + + /** + * @function query_models + * Perform the standard query logic, translating API parameters into mongo + * db queries in a fairly regimented manner. + * This middleware executes the query, returning the results as JSON + */ + function query_models (req, res, next) { + var query = req.query; + + // If "?count=" is present, use that number to decide how many to return. + if (!query.count) { + query.count = consts.ENTRIES_DEFAULT_COUNT; + } + + // perform the query + ctx.profile.list_query(query, function payload(err, profiles) { + return res.json(profiles); + }); + } + + // List profiles available + api.get('/profiles/', query_models); + // List profiles available api.get('/profile/', function(req, res) { ctx.profile.list(function (err, attribute) { diff --git a/lib/client/browser-settings.js b/lib/client/browser-settings.js index f45631f4b6f..0a8e1469a11 100644 --- a/lib/client/browser-settings.js +++ b/lib/client/browser-settings.js @@ -260,6 +260,7 @@ function init (client, serverSettings, $) { settings.thresholds = serverSettings.settings.thresholds; } + if (serverSettings.settings.enable) { settings.enable = serverSettings.settings.enable; } diff --git a/lib/client/careportal.js b/lib/client/careportal.js index a5c86232d8e..bff59a821c0 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -52,7 +52,7 @@ function init (client, $) { submitHooks = {}; _.forEach(careportal.allEventTypes, function each (event) { - inputMatrix[event.val] = _.pick(event, ['bg', 'insulin', 'carbs', 'protein', 'fat', 'prebolus', 'duration', 'percent', 'absolute', 'profile', 'split', 'reasons', 'targets']); + inputMatrix[event.val] = _.pick(event, ['otp','remoteCarbs', 'remoteAbsorption', 'remoteBolus', 'bg', 'insulin', 'carbs', 'protein', 'fat', 'prebolus', 'duration', 'percent', 'absolute', 'profile', 'split', 'reasons', 'targets']); submitHooks[event.val] = event.submitHook; }); } @@ -80,11 +80,18 @@ function init (client, $) { $('#reasonLabel').css('display', displayType(reasons && reasons.length > 0)); $('#targets').css('display', displayType(inputMatrix[eventType]['targets'])); + $('#otpLabel').css('display', displayType(inputMatrix[eventType]['otp'])); + $('#remoteCarbsLabel').css('display', displayType(inputMatrix[eventType]['remoteCarbs'])); + $('#remoteAbsorptionLabel').css('display', displayType(inputMatrix[eventType]['remoteAbsorption'])); + $('#remoteBolusLabel').css('display', displayType(inputMatrix[eventType]['remoteBolus'])); + $('#bg').css('display', displayType(inputMatrix[eventType]['bg'])); $('#insulinGivenLabel').css('display', displayType(inputMatrix[eventType]['insulin'])); + $('#carbsGivenLabel').css('display', displayType(inputMatrix[eventType]['carbs'])); $('#proteinGivenLabel').css('display', displayType(inputMatrix[eventType]['protein'])); $('#fatGivenLabel').css('display', displayType(inputMatrix[eventType]['fat'])); + $('#durationLabel').css('display', displayType(inputMatrix[eventType]['duration'])); $('#percentLabel').css('display', displayType(inputMatrix[eventType]['percent'] && $('#absolute').val() === '')); $('#absoluteLabel').css('display', displayType(inputMatrix[eventType]['absolute'] && $('#percent').val() === '')); @@ -99,6 +106,11 @@ function init (client, $) { careportal.reasonable(); + resetIfHidden(inputMatrix[eventType]['otp'], '#otp'); + resetIfHidden(inputMatrix[eventType]['remoteCarbs'], '#remoteCarbs'); + resetIfHidden(inputMatrix[eventType]['remoteAbsorption'], '#remoteAbsorption'); + resetIfHidden(inputMatrix[eventType]['remoteBolus'], '#remoteBolus'); + resetIfHidden(inputMatrix[eventType]['insulin'], '#insulinGiven'); resetIfHidden(inputMatrix[eventType]['carbs'], '#carbsGiven'); resetIfHidden(inputMatrix[eventType]['protein'], '#proteinGiven'); @@ -192,6 +204,12 @@ function init (client, $) { $('#eventType').val(''); $('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + client.settings.units); $('#meter').prop('checked', true); + + $('#otp').val(''); + $('#remoteCarbs').val(''); + $('#remoteAbsorption').val(''); + $('#remoteBolus').val(''); + $('#carbsGiven').val(''); $('#proteinGiven').val(''); $('#fatGiven').val(''); @@ -214,6 +232,10 @@ function init (client, $) { var data = { enteredBy: $('#enteredBy').val() , eventType: eventType + , otp: $('#otp').val() + , remoteCarbs: $('#remoteCarbs').val() + , remoteAbsorption: $('#remoteAbsorption').val() + , remoteBolus: $('#remoteBolus').val() , glucose: $('#glucoseValue').val().replace(',', '.') , reason: selectedReason , targetTop: $('#targetTop').val().replace(',', '.') @@ -226,11 +248,17 @@ function init (client, $) { , duration: times.msecs(parse_duration($('#duration').val())).mins < 1 ? $('#duration').val() : times.msecs(parse_duration($('#duration').val())).mins , percent: $('#percent').val() , profile: $('#profile').val() - , preBolus: parseInt($('#preBolus').val()) + , preBolus: $('#preBolus').val() , notes: $('#notes').val() , units: client.settings.units }; + data.preBolus = parseInt(data.preBolus); + + if (isNaN(data.preBolus)) { + delete data.preBolus; + } + var reasons = inputMatrix[eventType]['reasons']; var reason = _.find(reasons, function matches (r) { return r.name === selectedReason; @@ -273,7 +301,11 @@ function init (client, $) { data.splitExt = parseInt($('#insulinSplitExt').val()) || 0; } - return data; + let d = {}; + Object.keys(data).forEach(function(key) { + if (data[key] != "" && data[key] != null) d[key] = data[key]; + }); + return d; } careportal.save = function save (event) { @@ -327,6 +359,8 @@ function init (client, $) { } } + // TODO: add check for remote (Bolus, Carbs, Absorption) + return { allOk , messages @@ -350,6 +384,11 @@ function init (client, $) { text[text.length - 1] += ' ' + translate('Cancel'); } + pushIf(data.remoteCarbs, translate('Remote Carbs') + ': ' + data.remoteCarbs); + pushIf(data.remoteAbsorption, translate('Remote Absorption') + ': ' + data.remoteAbsorption); + pushIf(data.remoteBolus, translate('Remote Bolus') + ': ' + data.remoteBolus); + pushIf(data.otp, translate('One Time Pascode') + ': ' + data.otp); + pushIf(data.glucose, translate('Blood Glucose') + ': ' + data.glucose); pushIf(data.glucose, translate('Measurement Method') + ': ' + translate(data.glucoseType)); diff --git a/lib/client/chart.js b/lib/client/chart.js index 7a9b57bdd5d..d84dd0998b5 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -84,9 +84,13 @@ function init (client, d3, $) { function brushEnded () { // update the opacity of the context data points to brush extent + var selectedRange = chart.createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); + chart.context.selectAll('circle') .data(client.entries) - .style('opacity', function(d) { return renderer.highlightBrushPoints(d) }); + .style('opacity', function(d) { return renderer.highlightBrushPoints(d, from, to) }); } var extent = client.dataExtent(); @@ -251,6 +255,7 @@ function init (client, d3, $) { chart.createBrushedRange = function() { var brushedRange = chart.theBrush && d3.brushSelection(chart.theBrush.node()) || null; + var range = brushedRange && brushedRange.map(chart.xScale2.invert); var dataExtent = client.dataExtent(); @@ -267,6 +272,8 @@ function init (client, d3, $) { range[1] = new Date(end); range[0] = new Date(end - client.focusRangeMS); + // console.log('createBrushedRange: ', brushedRange, range); + return range; } @@ -375,7 +382,8 @@ function init (client, d3, $) { chart.theBrush.selectAll('rect') .attr('y', 0) - .attr('height', contextHeight); + .attr('height', contextHeight) + .attr('width', '100%'); // disable resizing of brush chart.context.select('.x.brush').select('.overlay').style('cursor', 'move'); @@ -507,7 +515,7 @@ function init (client, d3, $) { .attr('y', 0) .attr('height', contextHeight); - // console.log('Redrawing old brush with new dimensions: ', currentBrushExtent); + // console.log('chart.update(): Redrawing old brush with new dimensions: ', currentBrushExtent); // redraw old brush with new dimensions chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); @@ -578,7 +586,7 @@ function init (client, d3, $) { chart.xScaleBasals.domain(dataRange); - // console.log('Redrawing brush due to update: ', currentBrushExtent); + // console.log('chart.update(): Redrawing brush due to update: ', currentBrushExtent); chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); }; @@ -663,8 +671,7 @@ function init (client, d3, $) { renderer.addTreatmentProfiles(client); renderer.drawTreatments(client); - - // console.log('Redrawing brush due to update: ', currentBrushExtent); + // console.log('scrollUpdate(): Redrawing brush due to update: ', currentBrushExtent); chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js index e6eec7190c6..891f10d57d5 100644 --- a/lib/client/clock-client.js +++ b/lib/client/clock-client.js @@ -6,7 +6,7 @@ var client = {}; client.settings = browserSettings(client, window.serverSettings, $); -// console.log('settings', client.settings); +//console.log('settings', client.settings); // client.settings now contains all settings client.query = function query () { @@ -20,7 +20,7 @@ client.query = function query () { }); var secret = localStorage.getItem('apisecrethash'); - var src = '/api/v1/entries.json?count=3&t=' + new Date().getTime(); + var src = '/api/v1/entries.json?find[type]=sgv&count=3&t=' + new Date().getTime(); if (secret) { src += '&secret=' + secret; @@ -39,6 +39,7 @@ client.render = function render (xhr) { let rec; let delta; + // Get SGV, calculate DELTA xhr.forEach(element => { if (element.sgv && !rec) { rec = element; @@ -49,28 +50,65 @@ client.render = function render (xhr) { }); let $errorMessage = $('#errorMessage'); + let $inner = $('#inner'); // If no one measured value found => show "-?-" if (!rec) { if (!$errorMessage.length) { - $('#arrowDiv').append('
-?-
'); - $('#arrow').hide(); + $inner.after('
-?-
') } else { $errorMessage.show(); } + $inner.hide(); return; } else { $errorMessage.length && $errorMessage.hide(); - $('#arrow').show(); + $inner.show(); + } + + //Parse face parameters + let face = $inner.data('face').toLowerCase(); + + // Backward compatible + if (face === 'clock-color') { + face = 'c' + (window.serverSettings.settings.showClockLastTime ? 'y' : 'n') + '13-sg40-' + (window.serverSettings.settings.showClockDelta ? 'dt14-' : '') + 'nl-ar30-nl-ag6'; + } + else if (face === 'clock') { + face = 'bn0-sg40'; + } + else if (face === 'bgclock') { + face = 'bn0-sg30-ar18-nl-nl-tm26'; + } + else if (face === 'config') { + face = $inner.attr('data-face-config'); + $inner.empty(); + } + + let faceParams = face.split('-'); + let bgColor = false; + let staleMinutes = 13; + let alwaysShowTime = false; + + let clockCreated = ($inner.children().length > 0); + + for (let param in faceParams) { + if (param === '0') { + bgColor = (faceParams[param].substr(0, 1) === 'c'); // do we want colorful background? + alwaysShowTime = (faceParams[param].substr(1, 1) === 'y'); // always show "stale time" text? + staleMinutes = (faceParams[param].substr(2,2) - 0 >= 0) ? faceParams[param].substr(2,2) : 13; // threshold value (0=never) + } else if (!clockCreated){ + let div = '
0) ? ' style="' + ((faceParams[param].substr(0,2) === 'ar') ? 'height' : 'font-size') + ':' + faceParams[param].substr(2,2) + 'vmin"' : '') + '>
'; + $inner.append(div); + } } - - let last = new Date(rec.date); - let now = new Date(); // Convert BG to mmol/L if necessary. + let displayValue; + let deltaDisplayValue; + if (window.serverSettings.settings.units === 'mmol') { - var displayValue = window.Nightscout.units.mgdlToMMOL(rec.sgv); - var deltaDisplayValue = window.Nightscout.units.mgdlToMMOL(delta); + displayValue = window.Nightscout.units.mgdlToMMOL(rec.sgv); + deltaDisplayValue = window.Nightscout.units.mgdlToMMOL(delta); } else { displayValue = rec.sgv; deltaDisplayValue = Math.round(delta); @@ -80,18 +118,8 @@ client.render = function render (xhr) { deltaDisplayValue = '+' + deltaDisplayValue; } - // Insert the BG value text. - $('#bgnow').html(displayValue); - - // Insert the trend arrow. - $('#arrow').attr('src', '/images/' + (!rec.direction || rec.direction === 'NOT COMPUTABLE' ? 'NONE' : rec.direction) + '.svg'); - - // Time before data considered stale. - let staleMinutes = 13; - let threshold = 1000 * 60 * staleMinutes; - - // Toggle stale if necessary. - $('#bgnow').toggleClass('stale', (now - last > threshold)); + // Insert the delta value text. + $('.dt').html(deltaDisplayValue); // Generate and insert the clock. let timeDivisor = parseInt(client.settings.timeFormat ? client.settings.timeFormat : 12, 10); @@ -105,51 +133,24 @@ client.render = function render (xhr) { } let m = today.getMinutes(); if (m < 10) m = "0" + m; - $('#clock').text(h + ":" + m); - - /* global clockFace */ - if (clockFace === 'clock-color') { - - var bgHigh = window.serverSettings.settings.thresholds.bgHigh; - var bgLow = window.serverSettings.settings.thresholds.bgLow; - var bgTargetBottom = window.serverSettings.settings.thresholds.bgTargetBottom; - var bgTargetTop = window.serverSettings.settings.thresholds.bgTargetTop; + $('.tm').html(h + ":" + m); - var bgNum = parseFloat(rec.sgv); + // Color background + if (bgColor) { // These are the particular shades of red, yellow, green, and blue. - var red = 'rgba(213,9,21,1)'; - var yellow = 'rgba(234,168,0,1)'; - var green = 'rgba(134,207,70,1)'; - var blue = 'rgba(78,143,207,1)'; - - var elapsedMins = Math.round(((now - last) / 1000) / 60); - - // Insert the BG stale time text. - let staleTimeText; - if (elapsedMins == 0) { - staleTimeText = 'Just now'; - } - else if (elapsedMins == 1) { - staleTimeText = '1 minute ago'; - } - else { - staleTimeText = elapsedMins + ' minutes ago'; - } - $('#staleTime').text(staleTimeText); - - // Force NS to always show 'x minutes ago' - if (window.serverSettings.settings.showClockLastTime) { - $('#staleTime').css('display', 'block'); - } + let red = 'rgba(213,9,21,1)'; + let yellow = 'rgba(234,168,0,1)'; + let green = 'rgba(134,207,70,1)'; + let blue = 'rgba(78,143,207,1)'; - // Insert the delta value text. - $('#delta').html(deltaDisplayValue); + // Threshold values + let bgHigh = client.settings.thresholds.bgHigh; + let bgLow = client.settings.thresholds.bgLow; + let bgTargetBottom = client.settings.thresholds.bgTargetBottom; + let bgTargetTop = client.settings.thresholds.bgTargetTop; - // Show delta - if (window.serverSettings.settings.showClockDelta) { - $('#delta').css('display', 'inline-block'); - } + let bgNum = parseFloat(rec.sgv); // Threshold background coloring. if (bgNum < bgLow) { @@ -168,25 +169,52 @@ client.render = function render (xhr) { $('body').css('background-color', red); } - // Restyle body bg, and make the "x minutes ago" visible too. - if (now - last > threshold) { - $('body').css('background-color', 'grey'); - $('body').css('color', 'black'); - $('#arrow').css('filter', 'brightness(0%)'); + } + else { + $('body').css('background-color', 'black'); + } - if (!window.serverSettings.settings.showClockLastTime) { - $('#staleTime').css('display', 'block'); - } + // Time before data considered stale. + let threshold = 1000 * 60 * staleMinutes; - } else { - $('body').css('color', 'white'); - $('#arrow').css('filter', 'brightness(100%)'); + let last = new Date(rec.date); + let now = new Date(); + + let elapsedMins = Math.round(((now - last) / 1000) / 60); + + let thresholdReached = (now - last > threshold) && threshold > 0; - if (!window.serverSettings.settings.showClockLastTime) { - $('#staleTime').css('display', 'none'); - } + // Insert the BG value text, toggle stale if necessary. + $('.sg').toggleClass('stale', thresholdReached).html(displayValue); + if (thresholdReached || alwaysShowTime) { + let staleTimeText; + if (elapsedMins === 0) { + staleTimeText = 'Just now'; + } + else if (elapsedMins === 1) { + staleTimeText = '1 minute ago'; + } + else { + staleTimeText = elapsedMins + ' minutes ago'; } + + $('.ag').html(staleTimeText); + } + else { + $('.ag').html(''); + } + + // Insert the trend arrow. + let arrow = $('arrow').attr('src', '/images/' + (!rec.direction || rec.direction === 'NOT COMPUTABLE' ? 'NONE' : rec.direction) + '.svg'); + + // Restyle body bg + if (thresholdReached) { + $('body').css('background-color', 'grey').css('color', 'black'); + $('.ar').css('filter', 'brightness(0%)').html(arrow); + } else { + $('body').css('color', bgColor ? 'white' : 'grey'); + $('.ar').css('filter', bgColor ? 'brightness(100%)' : 'brightness(50%)').html(arrow); } }; diff --git a/lib/client/index.js b/lib/client/index.js index 29dc9593ae8..c7487c6b410 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -390,9 +390,12 @@ client.load = function load (serverSettings, callback) { brushExtent[0] = new Date(brushExtent[1].getTime() - client.focusRangeMS); - // console.log('Resetting brush in updateBrushToNow: ', brushExtent); + // console.log('updateBrushToNow(): Resetting brush: ', brushExtent); - chart.theBrush && chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); + if (chart.theBrush) { + chart.theBrush.call(chart.brush) + chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); + } if (!skipBrushing) { brushed(); @@ -415,7 +418,6 @@ client.load = function load (serverSettings, callback) { function brushed () { // Brush not initialized - console.log("brushed"); if (!chart.theBrush) { return; } @@ -426,11 +428,13 @@ client.load = function load (serverSettings, callback) { var brushedRange = d3.brushSelection(chart.theBrush.node()); + // console.log("brushed(): coordinates: ", brushedRange); + if (brushedRange) { brushExtent = brushedRange.map(chart.xScale2.invert); } - // console.log('Brushed to: ', brushExtent); + console.log('brushed(): Brushed to: ', brushExtent); if (!brushedRange || (brushExtent[1].getTime() - brushExtent[0].getTime() !== client.focusRangeMS)) { // ensure that brush updating is with the time range @@ -440,7 +444,7 @@ client.load = function load (serverSettings, callback) { brushExtent[1] = new Date(brushExtent[0].getTime() + client.focusRangeMS); } - // console.log('Updating brushed to: ', brushExtent); + // console.log('brushed(): updating to: ', brushExtent); chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); } diff --git a/lib/client/renderer.js b/lib/client/renderer.js index 818ee6f41d0..a3841695060 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -58,11 +58,7 @@ function init (client, d3) { } // get the desired opacity for context chart based on the brush extent - renderer.highlightBrushPoints = function highlightBrushPoints (data) { - var selectedRange = chart().createAdjustedRange(); - var from = selectedRange[0].getTime(); - var to = selectedRange[1].getTime(); - + renderer.highlightBrushPoints = function highlightBrushPoints (data, from, to) { if (client.latestSGV && data.mills >= from && data.mills <= to) { return chart().futureOpacity(data.mills - client.latestSGV.mills); } else { @@ -1038,6 +1034,9 @@ function init (client, d3) { renderer.addBasals = function addBasals (client) { + if (!client.settings.isEnabled('basal')) { + return; + } var mode = client.settings.extendedSettings.basal.render; var profile = client.sbx.data.profile; var linedata = []; diff --git a/lib/data/dataloader.js b/lib/data/dataloader.js index b0eafdd4fe0..3c2d8c6502d 100644 --- a/lib/data/dataloader.js +++ b/lib/data/dataloader.js @@ -361,8 +361,10 @@ function loadFood(ddata, ctx, callback) { } function loadDeviceStatus(ddata, env, ctx, callback) { + var retroDays = ONE_DAY; + if(env.extendedSettings.devicestatus && env.extendedSettings.devicestatus.days && env.extendedSettings.devicestatus.days == 2) retroDays = TWO_DAYS; var dateRange = { - $gte: new Date(ddata.lastUpdated - ONE_DAY).toISOString() + $gte: new Date( ddata.lastUpdated - (retroDays) ).toISOString() }; if (ddata.page && ddata.page.frame) { dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString(); diff --git a/lib/data/ddata.js b/lib/data/ddata.js index b5226124ccf..ed0830b270a 100644 --- a/lib/data/ddata.js +++ b/lib/data/ddata.js @@ -41,7 +41,7 @@ function init () { results.cals = ddata.cals; var profiles = _.cloneDeep(ddata.profiles); - if (profiles && profiles[0]) { + if (profiles && profiles[0] && profiles[0].store) { Object.keys(profiles[0].store).forEach(k => { if (k.indexOf('@@@@@') > 0) { delete profiles[0].store[k]; diff --git a/lib/plugins/loop.js b/lib/plugins/loop.js index 9b099dd188c..46d16738787 100644 --- a/lib/plugins/loop.js +++ b/lib/plugins/loop.js @@ -214,6 +214,8 @@ function init (ctx) { }); } + // TODO: add OTP entry + return [ { val: 'Temporary Override' @@ -229,6 +231,7 @@ function init (ctx) { , split: false , targets: false , reasons: reasonconf + , otp: true , submitHook: postLoopNotification }, { @@ -245,10 +248,28 @@ function init (ctx) { , split: false , targets: false , submitHook: postLoopNotification + }, + { + val: 'Remote Carbs Entry' + , name: 'Remote Carbs Entry' + , remoteCarbs: true + , remoteAbsorption: true + , otp: true + , submitHook: postLoopNotification + }, + { + val: 'Remote Bolus Entry' + , name: 'Remote Bolus Entry' + , remoteBolus: true + , otp: true + , submitHook: postLoopNotification } ]; }; + // TODO: Add event listener to customize labels + + loop.updateVisualisation = function updateVisualisation (sbx) { var prop = sbx.properties.loop; diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index 8bd251d4820..5ae6435c4c1 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -142,22 +142,28 @@ function init (profileData) { }; profile.getCurrentProfile = function getCurrentProfile (time, spec_profile) { + if (spec_profile) { + return spec_profile; + } else { + time = time || new Date().getTime(); + + time = time || Date.now(); + var minuteTime = Math.round(time / 60000) * 60000; + var cacheKey = ("profile" + minuteTime + spec_profile); + var returnValue = cache.get(cacheKey); + + if (returnValue) { + return returnValue; + } - time = time || Date.now(); - var minuteTime = Math.round(time / 60000) * 60000; - var cacheKey = ("profile" + minuteTime + spec_profile); - var returnValue = cache.get(cacheKey); + var pdataActive = profile.profileFromTime(time); + var data = profile.hasData() ? pdataActive : null; + var timeprofile = profile.activeProfileToTime(time); + returnValue = data && data.store[timeprofile] ? data.store[timeprofile] : {}; - if (returnValue) { + cache.put(cacheKey, returnValue, cacheTTL); return returnValue; } - - var data = profile.hasData() ? profile.data[0] : null; - var timeprofile = spec_profile || profile.activeProfileToTime(time); - returnValue = data && data.store[timeprofile] ? data.store[timeprofile] : {}; - - cache.put(cacheKey, returnValue, cacheTTL); - return returnValue; }; profile.getUnits = function getUnits (spec_profile) { @@ -225,10 +231,13 @@ function init (profileData) { profile.activeProfileToTime = function activeProfileToTime (time) { if (profile.hasData()) { - var timeprofile = profile.data[0].defaultProfile; time = Number(time) || new Date().getTime(); + + var pdataActive = profile.profileFromTime(time); + var timeprofile = pdataActive.defaultProfile; var treatment = profile.activeProfileTreatmentToTime(time); - if (treatment && profile.data[0].store && profile.data[0].store[treatment.profile]) { + + if (treatment && pdataActive.store && pdataActive.store[treatment.profile]) { timeprofile = treatment.profile; } return timeprofile; @@ -248,30 +257,31 @@ function init (profileData) { var treatment = null; if (profile.hasData()) { - profile.profiletreatments.forEach(function eachTreatment (t) { - if (time >= t.mills && t.mills >= profile.data[0].mills) { - var duration = times.mins(t.duration || 0).msecs; - if (duration != 0 && time < t.mills + duration) { - treatment = t; - // if profile switch contains json of profile inject it in to store to be findable by profile name - if (treatment.profileJson && !profile.data[0].store[treatment.profile]) { - if (treatment.profile.indexOf("@@@@@") < 0) - treatment.profile += "@@@@@" + treatment.mills; - let json = JSON.parse(treatment.profileJson); - profile.data[0].store[treatment.profile] = json; - } - } - if (duration == 0) { - treatment = t; - // if profile switch contains json of profile inject it in to store to be findable by profile name - if (treatment.profileJson && !profile.data[0].store[treatment.profile]) { - if (treatment.profile.indexOf("@@@@@") < 0) - treatment.profile += "@@@@@" + treatment.mills; - let json = JSON.parse(treatment.profileJson); - profile.data[0].store[treatment.profile] = json; - } + var pdataActive = profile.profileFromTime(time); + profile.profiletreatments.forEach(function eachTreatment(t) { + if (time >= t.mills && t.mills >= pdataActive.mills) { + var duration = times.mins(t.duration || 0).msecs; + if (duration != 0 && time < t.mills + duration) { + treatment = t; + // if profile switch contains json of profile inject it in to store to be findable by profile name + if (treatment.profileJson && !pdataActive.store[treatment.profile]) { + if (treatment.profile.indexOf("@@@@@") < 0) + treatment.profile += "@@@@@" + treatment.mills; + let json = JSON.parse(treatment.profileJson); + pdataActive.store[treatment.profile] = json; + } + } + if (duration == 0) { + treatment = t; + // if profile switch contains json of profile inject it in to store to be findable by profile name + if (treatment.profileJson && !pdataActive.store[treatment.profile]) { + if (treatment.profile.indexOf("@@@@@") < 0) + treatment.profile += "@@@@@" + treatment.mills; + let json = JSON.parse(treatment.profileJson); + pdataActive.store[treatment.profile] = json; + } + } } - } }); } @@ -286,6 +296,23 @@ function init (profileData) { else return name.substring(0, index); } + profile.profileFromTime = function profileFromTime (time) { + var profileData = null; + + if (profile.hasData()) { + profileData = profile.data[0]; + for (var i = 0; i < profile.data.length; i++) + { + if (Number(time) >= Number(profile.data[i].mills)) { + profileData = profile.data[i]; + break; + } + } + } + + return profileData; + } + profile.tempBasalTreatment = function tempBasalTreatment (time) { // Most queries for the data in reporting will match the latest found value, caching that hugely improves performance diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 6728a5e1860..b54aea0201e 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -452,6 +452,8 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio data.netBasalNegative[hour] = 0; }); + profile.loadData(datastorage.profiles); + profile.updateTreatments(datastorage.profileSwitchTreatments, datastorage.tempbasalTreatments, datastorage.combobolusTreatments); var bolusInsulin = 0; diff --git a/lib/report_plugins/profiles.js b/lib/report_plugins/profiles.js index 4aea2d040a8..f3bd9eb8d21 100644 --- a/lib/report_plugins/profiles.js +++ b/lib/report_plugins/profiles.js @@ -90,10 +90,12 @@ profiles.report = function report_profiles (datastorage) { function displayRanges (array, array2) { var text = ''; - for (var i = 0; i < array.length; i++) { - text += array[i].time + ' : ' + array[i].value + (array2 ? ' - ' + array2[i].value : '') + '
'; - } + if (array && array2) { + for (var i = 0; i < array.length; i++) { + text += array[i].time + ' : ' + array[i].value + (array2 ? ' - ' + array2[i].value : '') + '
'; + } + } return text; } }; diff --git a/lib/server/clocks.js b/lib/server/clocks.js index 56bce7b6f13..9926eefcf82 100644 --- a/lib/server/clocks.js +++ b/lib/server/clocks.js @@ -17,7 +17,7 @@ function clockviews() { const face = req.params.face; console.log('Clockface requested:', face); - res.render('shared.html', { + res.render('clock.html', { face, locals }); diff --git a/lib/server/loop.js b/lib/server/loop.js index a5e3aec5d8e..ca87892bba6 100644 --- a/lib/server/loop.js +++ b/lib/server/loop.js @@ -1,4 +1,4 @@ -'use strict'; +//'use strict'; const apn = require('apn'); @@ -9,6 +9,10 @@ function init (env, ctx) { } loop.sendNotification = function sendNotification (data, remoteAddress, completion) { + + // console.info("JAP"); + // console.info(data); + if (env.extendedSettings.loop.apnsKey === undefined || env.extendedSettings.loop.apnsKey.length == 0) { completion("Loop notification failed: LOOP_APNS_KEY not set."); return; @@ -63,7 +67,38 @@ function init (env, ctx) { alert = "Cancel Temporary Override"; } else if (data.eventType === 'Temporary Override') { payload["override-name"] = data.reason; + if (data.duration !== undefined && parseInt(data.duration) > 0) { + payload["override-duration-minutes"] = parseInt(data.duration); + } alert = data.reasonDisplay + " Temporary Override"; + } else if (data.eventType === 'Remote Carbs Entry') { + payload["carbs-entry"] = parseFloat(data.remoteCarbs); + if(payload["carbs-entry"] > 0.0 ) { + payload["absorption-time"] = 3.0; + if (data.remoteAbsorption !== undefined && parseFloat(data.remoteAbsorption) > 0.0) { + payload["absorption-time"] = parseFloat(data.remoteAbsorption); + } + if (data.otp !== undefined && data.otp.length > 0) { + payload["otp"] = ""+data.otp + } + alert = "Remote Carbs Entry: "+payload["carbs-entry"]+" grams\n"; + alert += "Absorption Time: "+payload["absorption-time"]+" hours"; + } else { + completion("Loop remote carbs failed. Incorrect carbs entry: ", data.remoteCarbs); + return; + } + + } else if (data.eventType === 'Remote Bolus Entry') { + payload["bolus-entry"] = parseFloat(data.remoteBolus); + if(payload["bolus-entry"] > 0.0 ) { + alert = "Remote Bolus Entry: "+payload["bolus-entry"]+" U\n"; + if (data.otp !== undefined && data.otp.length > 0) { + payload["otp"] = ""+data.otp + } + } else { + completion("Loop remote bolus failed. Incorrect bolus entry: ", data.remoteBolus); + return; + } } else { completion("Loop notification failed: Unhandled event type:", data.eventType); return; @@ -84,10 +119,6 @@ function init (env, ctx) { notification.expiry = Math.round((Date.now() / 1000)) + 60 * 5; // Allow this to enact within 5 minutes. notification.payload = payload; - if (data.duration && parseInt(data.duration) > 0) { - notification.payload["override-duration-minutes"] = parseInt(data.duration); - } - provider.send(notification, [loopSettings.deviceToken]).then( (response) => { if (response.sent && response.sent.length > 0) { completion(); diff --git a/lib/server/profile.js b/lib/server/profile.js index d456b590959..e4c43504256 100644 --- a/lib/server/profile.js +++ b/lib/server/profile.js @@ -1,5 +1,7 @@ 'use strict'; +var find_options = require('./query'); + function storage (collection, ctx) { var ObjectID = require('mongodb').ObjectID; @@ -27,6 +29,48 @@ function storage (collection, ctx) { return api( ).find({ }).sort({startDate: -1}).toArray(fn); } + function list_query (opts, fn) { + + storage.queryOpts = { + walker: {} + , dateField: 'startDate' + }; + + function limit () { + if (opts && opts.count) { + return this.limit(parseInt(opts.count)); + } + return this; + } + + return limit.call(api() + .find(query_for(opts)) + .sort(opts && opts.sort && query_sort(opts) || { startDate: -1 }), opts) + .toArray(fn); + } + + function query_for (opts) { + var retVal = find_options(opts, storage.queryOpts); + return retVal; + } + + function query_sort (opts) { + if (opts && opts.sort) { + var sortKeys = Object.keys(opts.sort); + + for (var i = 0; i < sortKeys.length; i++) { + if (opts.sort[sortKeys[i]] == '1') { + opts.sort[sortKeys[i]] = 1; + } + else { + opts.sort[sortKeys[i]] = -1; + } + } + return opts.sort; + } + } + + function last (fn) { return api().find().sort({startDate: -1}).limit(1).toArray(fn); } @@ -43,6 +87,7 @@ function storage (collection, ctx) { } api.list = list; + api.list_query = list_query; api.create = create; api.save = save; api.remove = remove; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index c28d19cac5b..46f925b9aba 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3213,9 +3213,9 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, "d3": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-5.12.0.tgz", - "integrity": "sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", "requires": { "d3-array": "1", "d3-axis": "1", @@ -3261,9 +3261,9 @@ "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" }, "d3-brush": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz", - "integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz", + "integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==", "requires": { "d3-dispatch": "1", "d3-drag": "1", @@ -3287,9 +3287,9 @@ "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" }, "d3-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz", - "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" }, "d3-contour": { "version": "1.3.2", @@ -3300,23 +3300,23 @@ } }, "d3-dispatch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", - "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" }, "d3-drag": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz", - "integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", "requires": { "d3-dispatch": "1", "d3-selection": "1" } }, "d3-dsv": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", - "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", "requires": { "commander": "2", "iconv-lite": "0.4", @@ -3324,9 +3324,9 @@ } }, "d3-ease": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", - "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz", + "integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==" }, "d3-fetch": { "version": "1.1.2", @@ -3348,45 +3348,45 @@ } }, "d3-format": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz", - "integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g==" + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz", + "integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw==" }, "d3-geo": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz", - "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.0.tgz", + "integrity": "sha512-NalZVW+6/SpbKcnl+BCO67m8gX+nGeJdo6oGL9H6BRUGUL1e+AtPcP4vE4TwCQ/gl8y5KE7QvBzrLn+HsKIl+w==", "requires": { "d3-array": "1" } }, "d3-hierarchy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", - "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" }, "d3-interpolate": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", - "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", "requires": { "d3-color": "1" } }, "d3-path": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz", - "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, "d3-polygon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", - "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" }, "d3-quadtree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", - "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" }, "d3-random": { "version": "1.1.2", @@ -3416,14 +3416,14 @@ } }, "d3-selection": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", - "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.1.tgz", + "integrity": "sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA==" }, "d3-shape": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", - "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", "requires": { "d3-path": "1" } @@ -3434,22 +3434,22 @@ "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "d3-time-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", - "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz", + "integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==", "requires": { "d3-time": "1" } }, "d3-timer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", - "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" }, "d3-transition": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", - "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", "requires": { "d3-color": "1", "d3-dispatch": "1", diff --git a/package.json b/package.json index ad221334050..920c5a48849 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "css-loader": "^1.0.1", "cssmin": "^0.4.3", "csv-stringify": "^5.3.5", - "d3": "^5.12.0", + "d3": "^5.16.0", "easyxml": "^2.0.1", "ejs": "^2.6.2", "errorhandler": "^1.5.1", diff --git a/static/report/js/report.js b/static/report/js/report.js index f7b02ec44af..b0cbd1abab8 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -459,11 +459,14 @@ if (loadeddays === dayscount) { sorteddaystoshow.sort(); var from = sorteddaystoshow[0]; + var dFrom = sorteddaystoshow[0]; + var dTo = sorteddaystoshow[(sorteddaystoshow.length - 1)]; + if (options.order === report_plugins.consts.ORDER_NEWESTONTOP) { sorteddaystoshow.reverse(); } - loadProfileSwitch(from, function loadProfileSwitchCallback() { - loadProfiles(function loadProfilesCallback() { + loadProfileSwitch(dFrom, function loadProfileSwitchCallback() { + loadProfilesRange(dFrom, dTo, sorteddaystoshow.length, function loadProfilesCallback() { $('#info > b').html('' + translate('Rendering') + ' ...'); window.setTimeout(function () { showreports(options); @@ -709,7 +712,7 @@ }); } - function loadProfileSwitch(from, callback) { + function loadProfileSwitch (from, callback) { $('#info > b').html(''+translate('Loading profile switch data') + ' ...'); var tquery = '?find[eventType]=Profile Switch' + '&find[created_at][$lte]=' + new Date(from).toISOString() + '&count=1'; $.ajax('/api/v1/treatments.json'+tquery, { @@ -743,6 +746,69 @@ }).done(callback); } + function loadProfilesRange (dateFrom, dateTo, dayCount, callback) { + $('#info > b').html('' + translate('Loading profile range') + ' ...'); + + $.when( + loadProfilesRangeCore(dateFrom, dateTo, dayCount), + loadProfilesRangePrevious(dateFrom), + loadProfilesRangeNext(dateTo) + ) + .done(callback) + .fail(function () { + datastorage.profiles = []; + }); + } + + function loadProfilesRangeCore (dateFrom, dateTo, dayCount) { + $('#info > b').html('' + translate('Loading core profiles') + ' ...'); + + //The results must be returned in descending order to work with key logic in routines such as getCurrentProfile + var tquery = '?find[startDate][$gte]=' + new Date(dateFrom).toISOString() + '&find[startDate][$lte]=' + new Date(dateTo).toISOString() + '&sort[startDate]=-1&count=' + dayCount; + + return $.ajax('/api/v1/profiles' + tquery, { + headers: client.headers(), + async: false, + success: function (records) { + datastorage.profiles = records; + } + }); + } + + function loadProfilesRangePrevious (dateFrom) { + $('#info > b').html('' + translate('Loading previous profile') + ' ...'); + + //Find first one before the start date and add to datastorage.profiles + var tquery = '?find[startDate][$lt]=' + new Date(dateFrom).toISOString() + '&sort[startDate]=-1&count=1'; + + return $.ajax('/api/v1/profiles' + tquery, { + headers: client.headers(), + async: false, + success: function (records) { + records.forEach(function (r) { + datastorage.profiles.push(r); + }); + } + }); + } + + function loadProfilesRangeNext (dateTo) { + $('#info > b').html('' + translate('Loading next profile') + ' ...'); + + //Find first one after the end date and add to datastorage.profiles + var tquery = '?find[startDate][$gt]=' + new Date(dateTo).toISOString() + '&sort[startDate]=1&count=1'; + + return $.ajax('/api/v1/profiles' + tquery, { + headers: client.headers(), + async: false, + success: function (records) { + records.forEach(function (r) { + //must be inserted as top to maintain profiles being sorted by date in descending order + datastorage.profiles.unshift(r); + }); + } + }); + } function processData(data, day, options, callback) { if (daystoshow[day].treatmentsonly) { diff --git a/test b/test new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/client.renderer.test.js b/tests/client.renderer.test.js index ca81e7d99e8..5dc707ab2b1 100644 --- a/tests/client.renderer.test.js +++ b/tests/client.renderer.test.js @@ -64,7 +64,10 @@ describe('renderer', () => { describe(`data.mills ${extent.mills} and chart().brush.extent() times ${extent.times}`, () => { it(extent.expectation, () => { - renderer(mockClient, {}).highlightBrushPoints(mockData).should.equal(extent.expectedOpacity); + var selectedRange = mockClient.chart.createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); + renderer(mockClient, {}).highlightBrushPoints(mockData, from, to).should.equal(extent.expectedOpacity); }); }); }); diff --git a/tests/profile.test.js b/tests/profile.test.js index 373f0479d9d..5928ccd2618 100644 --- a/tests/profile.test.js +++ b/tests/profile.test.js @@ -188,5 +188,199 @@ describe('Profile', function ( ) { dia.should.equal(9); }); + var multiProfileData = + [ + { + "startDate": "2015-06-25T00:00:00.000Z", + "defaultProfile": "20150625-1", + "store": { + "20150625-1": { + "dia": "4", + "timezone": moment.tz().zoneName(), //Assume these are in the localtime zone so tests pass when not on UTC time + "startDate": "1970-01-01T00:00:00.000Z", + 'sens': [ + { + 'time': '00:00', + 'value': 12 + }, + { + 'time': '02:00', + 'value': 13 + }, + { + 'time': '07:00', + 'value': 14 + } + ], + 'carbratio': [ + { + 'time': '00:00', + 'value': 16 + }, + { + 'time': '06:00', + 'value': 15 + }, + { + 'time': '14:00', + 'value': 17 + } + ], + 'carbs_hr': 30, + 'target_low': 4.5, + 'target_high': 8, + "units": "mmol", + "basal": [ + { + "time": "00:00", + "value": "0.5", + "timeAsSeconds": "0" + }, + { + "time": "09:00", + "value": "0.25", + "timeAsSeconds": "32400" + }, + { + "time": "12:30", + "value": "0.9", + "timeAsSeconds": "45000" + }, + { + "time": "17:00", + "value": "0.3", + "timeAsSeconds": "61200" + }, + { + "time": "20:00", + "value": "1", + "timeAsSeconds": "72000" + } + ] + } + }, + "units": "mmol", + "mills": "1435190400000" + }, + { + "startDate": "2015-06-21T00:00:00.000Z", + "defaultProfile": "20190621-1", + "store": { + "20190621-1": { + "dia": "4", + "timezone": moment.tz().zoneName(), //Assume these are in the localtime zone so tests pass when not on UTC time + "startDate": "1970-01-01T00:00:00.000Z", + 'sens': [ + { + 'time': '00:00', + 'value': 11 + }, + { + 'time': '02:00', + 'value': 10 + }, + { + 'time': '07:00', + 'value': 9 + } + ], + 'carbratio': [ + { + 'time': '00:00', + 'value': 12 + }, + { + 'time': '06:00', + 'value': 13 + }, + { + 'time': '14:00', + 'value': 14 + } + ], + 'carbs_hr': 35, + 'target_low': 4.2, + 'target_high': 9, + "units": "mmol", + "basal": [ + { + "time": "00:00", + "value": "0.3", + "timeAsSeconds": "0" + }, + { + "time": "09:00", + "value": "0.4", + "timeAsSeconds": "32400" + }, + { + "time": "12:30", + "value": "0.5", + "timeAsSeconds": "45000" + }, + { + "time": "17:00", + "value": "0.6", + "timeAsSeconds": "61200" + }, + { + "time": "23:00", + "value": "0.7", + "timeAsSeconds": "82800" + } + ] + } + }, + "units": "mmol", + "mills": "1434844800000" + } + ]; + + var multiProfile = require('../lib/profilefunctions')(multiProfileData); + + var noon = new Date('2015-06-22 12:00:00').getTime(); + var threepm = new Date('2015-06-26 15:00:00').getTime(); + + it('should return profile units when configured', function () { + var value = multiProfile.getUnits(); + value.should.equal('mmol'); + }); + + + it('should know what the basal rate is at 12:00 with multiple profiles', function () { + var value = multiProfile.getBasal(noon); + value.should.equal(0.4); + }); + + it('should know what the basal rate is at 15:00 with multiple profiles', function () { + var value = multiProfile.getBasal(threepm); + value.should.equal(0.9); + }); + + it('should know what the carbratio is at 12:00 with multiple profiles', function () { + var carbRatio = multiProfile.getCarbRatio(noon); + carbRatio.should.equal(13); + }); + + it('should know what the carbratio is at 15:00 with multiple profiles', function () { + var carbRatio = multiProfile.getCarbRatio(threepm); + carbRatio.should.equal(17); + }); + + it('should know what the sensitivity is at 12:00 with multiple profiles', function () { + var dia = multiProfile.getSensitivity(noon); + dia.should.equal(9); + }); + + it('should know what the sensitivity is at 15:00 with multiple profiles', function () { + var dia = multiProfile.getSensitivity(threepm); + dia.should.equal(14); + }); + + + it('should select the correct profile for 15:00 with multiple profiles', function () { + var curProfile = multiProfile.getCurrentProfile(threepm); + curProfile.carbs_hr.should.equal(30); + }); }); \ No newline at end of file diff --git a/views/clockviews/bgclock.css b/views/clockviews/bgclock.css deleted file mode 100644 index 3e2cbef246f..00000000000 --- a/views/clockviews/bgclock.css +++ /dev/null @@ -1,28 +0,0 @@ -.inner { - -webkit-transform: translateY(-2%); -} - -#bgnow, #arrowDiv { - display: flex; - flex-grow: 0; - font-weight: 700; - font-size: 30vmin; - padding: 0 20px; - margin: 0; -} - -img#arrow { - height: 18vmin; - filter: brightness(50%); - -webkit-transform: translateY(5%); -} - -#clock { - font-weight: 700; - font-size: 25vmin; - display: inline; -} - -.stale { - text-decoration: line-through; -} \ No newline at end of file diff --git a/views/clockviews/clock-color.css b/views/clockviews/clock-color.css deleted file mode 100644 index 6a6796ef823..00000000000 --- a/views/clockviews/clock-color.css +++ /dev/null @@ -1,32 +0,0 @@ -body { - color: white; -} - -#trend { - -webkit-transform: translateX(1%); - -webkit-flex-direction: column; - flex-direction: column; -} - -#bgnow { - display: inline-block; - vertical-align: middle; -} - -#delta { - font-size: 16vmin; - vertical-align: middle; -} - -#innerTrend { - word-spacing: 2em; -} - -#arrowDiv { - flex-grow: 1; - text-align: center; -} - -img#arrow { - height: 30vmin; -} \ No newline at end of file diff --git a/views/clockviews/clock-config.css b/views/clockviews/clock-config.css new file mode 100644 index 00000000000..0d5f2b8b912 --- /dev/null +++ b/views/clockviews/clock-config.css @@ -0,0 +1,39 @@ +#config-form { + position: fixed; + top: 10px; + left: 10px; + width: 250px; + min-width: 220px; + background: white; + color: black; + opacity: 0.8; + padding: 1%; + font-size: 10px; +} +#config-form p { + margin: 15px; + text-align: left; +} +input.elmt { + width: 120px; +} +select { + width: 100%; +} +#facename { + font-size: 7px; +} +#clocklink { + font-size: 18px; +} +#clocklink:link, #clocklink:visited { + background-color: #f44336; + color: white; + padding: 14px 25px; + text-align: center; + text-decoration: none; + display: inline-block; +} +#clocklink:hover, #clocklink:active { + background-color: red; +} \ No newline at end of file diff --git a/views/clockviews/clock-config.html b/views/clockviews/clock-config.html new file mode 100644 index 00000000000..fff92bb65cb --- /dev/null +++ b/views/clockviews/clock-config.html @@ -0,0 +1,65 @@ +
+

Clock view configurator

+
+

+ + +

+

+ + +

+

SGV age threshold: minutes

+

+

Size:

+

Size:

+

Size:

+

Size:

+

Size:

+

+ Open my clock view! +
cy10
+
+
+ \ No newline at end of file diff --git a/views/clockviews/clock-shared.css b/views/clockviews/clock-shared.css index 83328fe4114..fc624a52c00 100644 --- a/views/clockviews/clock-shared.css +++ b/views/clockviews/clock-shared.css @@ -3,57 +3,50 @@ body { margin: 0 0; padding: 0; overflow: hidden; - font-family: 'Open Sans'; + font-family: 'Open Sans', Arial, Helvetica, sans-serif; color: grey; background-color: black; } main { + height: 100vh; +} + +#inner { display: -webkit-box; display: -ms-flexbox; display: -webkit-flex; display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - -webkit-align-items: center; align-items: center; - height: 100vh; -} - -.inner { + justify-content: center; + align-content: center; + flex-flow: wrap; + height: 100%; width: 100%; - -webkit-transform: translateY(-5%); } -#bgnow { - font-weight: 700; - font-size: 40vmin; +#inner div { + margin-right: 2vmin; + margin-left: 2vmin; + line-height: 1em; } -#trend { - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - -ms-flex-align: center; - -webkit-align-items: center; - align-items: center; - justify-content: center; - -webkit-flex-direction: row; - flex-direction: row; +#inner div img { + height: 100%; } -#staleTime { - flex-grow: 1; - font-size: 6vmin; - display: none; +#inner div.nl { + width: 100%; + margin: 0; + height: 3vmin; } -#clock { - display: none; +#errorMessage { + font-size: 25em; } -#delta { - display: none; +.stale { + text-decoration: line-through; } .close { diff --git a/views/clockviews/clock.css b/views/clockviews/clock.css deleted file mode 100644 index 96ffe68b84a..00000000000 --- a/views/clockviews/clock.css +++ /dev/null @@ -1,5 +0,0 @@ -#trend { - -webkit-transform: translateX(1%); - -webkit-flex-direction: column; - flex-direction: column; -} \ No newline at end of file diff --git a/views/clockviews/shared.html b/views/clockviews/clock.html similarity index 88% rename from views/clockviews/shared.html rename to views/clockviews/clock.html index beac7dc0f2e..2893aec3612 100644 --- a/views/clockviews/shared.html +++ b/views/clockviews/clock.html @@ -1,5 +1,5 @@ - + @@ -21,23 +21,16 @@
-
-
-
- - -
-
arrow
-
-
-
+
>
@@ -69,7 +62,7 @@ script.src = src; document.head.appendChild(script); //or something of the likes - + <%if (face !== 'config') { %> var buttonVisible = true; function hideClose () { @@ -99,8 +92,11 @@ window.addEventListener('click', function() { showClose(); }); - + <% } %> + <%if (face == 'config') { %> + <%- include('clock-config.html', {}); %> + <% } %> diff --git a/views/index.html b/views/index.html index 6ef0dc2cce6..c0f6b31613e 100644 --- a/views/index.html +++ b/views/index.html @@ -174,6 +174,7 @@ Clock Color Simple + [+]
@@ -339,7 +340,24 @@ Sensor
-
+ + + + + +
+