diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000000..ff3059c3f09 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} \ No newline at end of file diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index c2587b247fc..00000000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "./static/bower_components" -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000000..974a562c7c6 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,23 @@ +module.exports = { + "plugins": [ ], + "extends": [ + "eslint:recommended" + ], + "parser": "babel-eslint", + "env": { + "browser": true, + "commonjs": true, + "es6": true, + "node": true, + "mocha": true, + "jquery": true + }, + "rules": { + "no-unused-vars": [ + "error", + { + "varsIgnorePattern": "should|expect" + } + ] + } + }; \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/--bug-report.md b/.github/ISSUE_TEMPLATE/--bug-report.md new file mode 100644 index 00000000000..b0d61217880 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--bug-report.md @@ -0,0 +1,30 @@ +--- +name: "\U0001F41BBug report" +about: Create a report to help us improve things +label: bug + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Your setup information** +- What version of Nightscout (e.g. 0.10.3) +- What type of CGM, and how do you get your data there? (e.g. G4 and ShareBridge, or wired receiver, etc.) +- Is your issue specific to a browser (Firefox/Safari/Chrome?) or a device (Android phone, etc.)? + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/--feature-request--.md b/.github/ISSUE_TEMPLATE/--feature-request--.md new file mode 100644 index 00000000000..a94a261abf8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--feature-request--.md @@ -0,0 +1,17 @@ +--- +name: "\U0001F4A1Feature request\U0001F4A1" +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/--individual-troubleshooting-help.md b/.github/ISSUE_TEMPLATE/--individual-troubleshooting-help.md new file mode 100644 index 00000000000..ff6e27b7682 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--individual-troubleshooting-help.md @@ -0,0 +1,16 @@ +--- +name: "\U0001F198Individual troubleshooting help" +about: Getting help with your own individual setup of Nightscout + +--- + +Having issues getting Nightscout up and running? Instead of creating an issue here, please use one of the existing support channels for Nightscout. + +The main support channel is on Facebook: please join the CGM In The Cloud Facebook group (https://www.facebook.com/groups/cgminthecloud) and start a post there. + +**Suggestions to include in your post when you are asking for help:** +1. Include what you are trying to do: ("*I am trying to set up Nightscout for the first time.*") +2. Include which step you are on and what the problem is: ("*I deployed on Heroku, but I'm not seeing any BG data.*") +3. If possible, include a link to the version of documentation you are following ("*I'm following the OpenAPS Nightscout setup docs (https://openaps.readthedocs.io/en/latest/docs/While%20You%20Wait%20For%20Gear/nightscout-setup.html#nightscout-setup-with-heroku)*") + +Other places you can find support and assistance for Nightscout include Gitter's [nightscout/public](https://gitter.im/nightscout/public) channel. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000000..23cc61e5ef7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,32 @@ +name: CI test + +on: [push] + +jobs: + build: + + runs-on: ubuntu-16.04 + + strategy: + matrix: + node-version: [10.x, 12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: npm install + - name: Install MongoDB + run: | + wget -qO - https://www.mongodb.org/static/pgp/server-3.6.asc | sudo apt-key add - + echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.6 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.6.list + sudo apt-get update + sudo apt-get install -y mongodb-org + sudo apt-get install -y --allow-downgrades mongodb-org=3.6.14 mongodb-org-server=3.6.14 mongodb-org-shell=3.6.14 mongodb-org-mongos=3.6.14 mongodb-org-tools=3.6.14 + - name: Start MongoDB + run: sudo systemctl start mongod + - name: Run tests + run: npm run-script test-ci diff --git a/.gitignore b/.gitignore index 2cb28800650..1cf7ab06f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,8 @@ bundle/bundle.out.js .idea/ *.iml my.env +my.*.env -*.env static/bower_components/ .*.sw? .DS_Store @@ -24,3 +24,9 @@ npm-debug.log *.heapsnapshot /tmp +/.vs +/cgm-remote-monitor.njsproj +/cgm-remote-monitor.sln +/obj/Debug +/bin +/*.bat diff --git a/.jsbeautifyrc b/.jsbeautifyrc new file mode 100644 index 00000000000..1c15d3872ce --- /dev/null +++ b/.jsbeautifyrc @@ -0,0 +1,16 @@ +{ + "indent_size": 2 + , "indent_char": " " + , "comma_first": true + , "keep-array-indentation": true + , "space_after_named_function": true + , "space_after_anon_function": true + , "end_with_newline": true + , "brace_style": "collapse,preserve-inline" + , "space_in_brace": true + , "space-in-paren": false + , "break-chained-methods": false + , "max-preserve-newlines": 2 + , "space-after-anon-function": false + , "indent-empty-lines": false +} diff --git a/.nvmrc b/.nvmrc index ed13033b848..89da89da65c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -8.11.x +10.16.0 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index d8ff4c865ff..78b056b8dbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,30 @@ -language: node_js -os: osx -node_js: - - "8" -before_install: - - if [[ `npm --version` != "5.8.0" ]]; then npm install -g npm@latest; npm --version; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi - # https://github.com/Homebrew/homebrew-core/issues/26358 - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink python; fi - # "brew install" can succeed but return 1 if it has "caveats". - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install mongodb || true; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew services start mongodb; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install docker || true; fi +sudo: required +dist: xenial + +node_js-steps: &node_js-steps + language: node_js + before_install: + - if [[ `npm --version` != "6.4.1" ]]; then npm install -g npm@latest; npm --version; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi + # https://github.com/Homebrew/homebrew-core/issues/26358 + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink python; fi + # "brew install" can succeed but return 1 if it has "caveats". + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install mongodb || true; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew services start mongodb; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install docker || true; fi + script: make travis + after_success: + - nvm version + - if [[ ! -z "$DOCKER_USER" ]]; then docker login -u ${DOCKER_USER} -p ${DOCKER_PASS} && git checkout -- . && git clean -fd . && make docker_release; fi + after_script: make report + services: + - mongodb + - docker matrix: - fast_finish: true -services: - - mongodb - - docker -script: make travis -after_success: - - nvm version - - if [[ ! -z "$DOCKER_USER" ]]; then docker login -u ${DOCKER_USER} -p ${DOCKER_PASS} && git checkout -- . && git clean -fd . && make docker_release; fi -after_script: make report + allow_failures: + node_js: "node" + include: + - node_js: "10" + <<: *node_js-steps + - node_js: "12" # Latest Node is not supported, and recommend, but we'll test it to know incompatibility issues + <<: *node_js-steps diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5be3a99d625..c8e87e2db4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,26 +3,29 @@ **Table of Contents** - [Contributing to cgm-remote-monitor](#contributing-to-cgm-remote-monitor) - - [Design](#design) + - [Design & new features](#design--new-features) - [Develop on `dev`](#develop-on-dev) - [Style Guide](#style-guide) - [Create a prototype](#create-a-prototype) - [Submit a pull request](#submit-a-pull-request) + - [Bug fixing](#bug-fixing) - [Comments and issues](#comments-and-issues) - [Co-ordination](#co-ordination) - [Other Dev Tips](#other-dev-tips) + - [List of Contributors](#list-of-contributors) + - [Core developers, contributing developers, coordinators and documentation writers](#core-developers-contributing-developers-coordinators-and-documentation-writers) + - [Plugin contributors](#plugin-contributors) + - [Translators](#translators) + - [List of all contributors](#list-of-all-contributors) - # Contributing to cgm-remote-monitor [![Build Status][build-img]][build-url] [![Dependency Status][dependency-img]][dependency-url] [![Coverage Status][coverage-img]][coverage-url] -[![Gitter chat][gitter-img]][gitter-url] -[![Stories in Ready][ready-img]][waffle] -[![Stories in Progress][progress-img]][waffle] +[![Discord chat][discord-img]][discord-url] [build-img]: https://img.shields.io/travis/nightscout/cgm-remote-monitor.svg [build-url]: https://travis-ci.org/nightscout/cgm-remote-monitor @@ -30,29 +33,52 @@ [dependency-url]: https://david-dm.org/nightscout/cgm-remote-monitor [coverage-img]: https://img.shields.io/coveralls/nightscout/cgm-remote-monitor/master.svg [coverage-url]: https://coveralls.io/r/nightscout/cgm-remote-monitor?branch=master -[gitter-img]: https://img.shields.io/badge/Gitter-Join%20Chat%20%E2%86%92-1dce73.svg -[gitter-url]: https://gitter.im/nightscout/public -[ready-img]: https://badge.waffle.io/nightscout/cgm-remote-monitor.svg?label=ready&title=Ready -[waffle]: https://waffle.io/nightscout/cgm-remote-monitor -[progress-img]: https://badge.waffle.io/nightscout/cgm-remote-monitor.svg?label=in+progress&title=In+Progress +[discord-img]: https://img.shields.io/discord/629952586895851530?label=discord%20chat +[discord-url]: https://discordapp.com/channels/629952586895851530/629952669967974410 + +## Installation for development -## Design +Nightscout is a Node.js application. The basic installation of the software for local purposes is: -Participate in the design process by creating an issue to discuss your -design. +1. Clone the software to your local machine using git +2. Install Node from https://nodejs.org/en/download/ +2. Use `npm` to install Nightscout dependencies by invoking `npm install` in the project directory. Note the + dependency installation has to be done using a non-root user - _do not use root_ for development and hosting + the software! +3. Get a Mongo database by either installing Mongo locally, or get a free cloud account from mLab or MongoDB Atlas. +4. Configure Nightscout by copying `my.env.template` to `my.env` and run it - see the next chapter in the instructions ## Develop on `dev` -We develop on the `dev` branch. -You can get the dev branch checked out using `git checkout dev`. +We develop on the `dev` branch. All new pull requests should be targeted to `dev`. The `master` branch is only used for distributing the latest version of the tested sources. + +You can get the `dev` branch checked out using `git checkout dev`. + +Once checked out, install the dependencies using `npm install`, then copy the included `my.env.template`file to `my.env` and edit the file to include your settings (like the Mongo URL). Leave the `NODE_ENV=development` line intact. Once set, run the site using `npm run dev`. This will start Nightscout in the development mode, with different code packaging rules and automatic restarting of the server using nodemon, when you save changed files on disk. The client also hot-reloads new code in, but it's recommended to reload the website after changes due to the way the plugin sandbox works. + +Note the template sets `INSECURE_USE_HTTP` to `true` to enable the site to work over HTTP in local development. + +If you want to additionaly test the site in production mode, create a file called `my.prod.env` that's a copy of the dev file but with `NODE_ENV=production` and start the site using `npm run prod`. + +## REST API + +Nightscout implements a REST API for data syncronization. The API is documented using Swagger. To access the documentation for the API, run Nightscout locally and load the documentation from /api-docs (or read the associated swagger.json and swagger.yaml files locally). + +Note all dates used to access the API and dates stored in the objects are expected to comply with the ISO-8601 format and be deserializable by the Javascript Date class. Of note here is the dates can contain a plus sign which has a special meaning in URL encoding, so when issuing requests that place dates to the URL, take special care to ensure the data is properly URL encoded. + +## Design & new features + +If you intend to add a new feature, please allow the community to participate in the design process by creating an issue to discuss your design. For new features, the issue should describe what use cases the new feature intends to solve, or which existing use cases are being improved. + +Note Nightscout has a plugin architecture for adding new features. We expect most code for new features live inside a Plugin, so the code retains a clear separation of concerns. If the Plugin API doesn't implement all features you need to implement your feature, please discuss with us on adding those features to the API. Note new features should under almost no circumstances require changes to the existing plugins. ## Style Guide -Some simple rules, that will make it easier to maintain our codebase: +Some simple rules that will make it easier to maintain our codebase: -* All indenting should use 2 space where possible (js, css, html, etc) -* A space before function parameters, such as: `function boom (name, callback) { }`, this makes searching for calls easier -* Name your callback functions, such as `boom('the name', function afterBoom ( result ) { }` +* All indenting should use 2 space where possible (js, css, html, etc). +* Include a space before function parameters, such as: `function boom (name, callback) { }`, this makes searching for function calls easier. +* Name your callback functions, such as `boom('the name', function afterBoom ( result ) { }`. * Don't include author names in the header of your files, if you need to give credit to someone else do it in the commit comment. * Use single quotes. * Use the comma first style, for example: @@ -65,58 +91,183 @@ Some simple rules, that will make it easier to maintain our codebase: }; ``` +If in doubt, format your code with `js-beautify --indent-size 2 --comma-first --keep-array-indentation` + ## Create a prototype -Fork cgm-remote-monitor and create a branch. -You can create a branch using `git checkout -b wip/add-my-widget`. -This creates a new branch called `wip/add-my-widget`. The `wip` -stands for work in progress and is a common prefix so that when know -what to expect when reviewing many branches. +Fork cgm-remote-monitor and create a branch. You can create a branch using `git checkout -b wip/add-my-widget`. This creates a new branch called `wip/add-my-widget`. The "`wip`" stands for work-in-progress and is a common prefix so that we know what to expect when reviewing many branches. ## Submit a pull request -When you are done working with your prototype, it can be tempting to -post on popular channels such as Facebook. We encourage contributors -to submit their code for review, debate, and release before announcing -features on social media. +When you are done working with your prototype, it can be tempting to post on popular channels such as Facebook. We encourage contributors to submit their code for review, debate, and release before announcing features on social media. + +This can be done by checking your code `git commit -avm 'my improvements are here'`, the branch you created back to your own fork. This will probably look something like `git push -u origin wip/add-my-widget`. + +Now that the commits are available on github, you can click on the compare buttons on your fork to create a pull request. Make sure to select [Nightscout's `dev` branch](https://github.com/nightscout/cgm-remote-monitor/tree/dev). + +We assume all new Pull Requests are at least smoke tested by the author and all code in the PR actually works. Please include a description of what the features do and rationalize why the changes are needed. + +If you add any new NPM module dependencies, you have to rationalize why they are needed - we prefer pull requests that reduce dependencies, not add them. Before releasing a a new version, we check with `npm audit` if our dependencies don't have known security issues. -This can be done by checking your code `git commit -avm 'my -improvements are here'`, the branch you created back to your own +When adding new features that add configuration options, please ensure the `README` document is amended with information on the new configuration. + +## Bug fixing + +If you've fixed a bug, please consider adding a unit test to the `/tests` folder that reproduces the original bug without the change. + +Try to identify the root cause of the issue and fix the issue. Pull requests that simply add null checks to hide issues are unlikely to be accepted. + +This can be done by committing your code `git commit -avm 'my +improvements are here'`, and pushing it to the branch you created on your own fork. This will probably look something like `git push -u origin wip/add-my-widget`. -Now that the commits are available on github, you can click on the -compare buttons on your fork to create a pull request. Make sure to -select [Nightscout's `dev` branch](https://github.com/nightscout/cgm-remote-monitor/tree/dev). +Please include instructions how to test the changes. ## Comments and issues -We encourage liberal use of the comments, including images where -appropriate. +We encourage liberal use of the comments, including images where appropriate. ## Co-ordination -Most cgm-remote-monitor hackers use github's ticketing system, along with Facebook cgm-in-the-cloud, and -gitter. +We primarily use GitHub's ticketing system for discussing PRs and bugs, and [Discord][discord-url] for general development chatter. -We use git-flow, with `master` as our production, stable branch, and -`dev` is used to queue up for upcoming releases. Everything else is -done on branches, hopefully with names that indicate what to expect. +We use git-flow, with `master` as our production, stable branch, and `dev` is used to queue up for upcoming releases. Everything else is done on branches, hopefully with names that indicate what to expect. -Once `dev` has been reviewed and people feel it's time to release, we -follow the git-flow release process, which creates a new tag and bumps -the version correctly. See sem-ver for versioning strategy. +Once `dev` has been reviewed and people feel it's time to release, we follow the git-flow release process, which creates a new tag and bumps the version correctly. See sem-ver for versioning strategy. -Every commit is tested by travis. We encourage adding tests to -validate your design. We encourage discussing your use cases to help -everyone get a better understanding of your design. +Every commit is tested by travis. We encourage adding tests to validate your design. We encourage discussing your use cases to help everyone get a better understanding of your design. ## Other Dev Tips -* Join the [Gitter chat][gitter-url] -* Get a local dev environment setup if you haven't already -* Try breaking up big features/improvements into small parts. It's much easier to accept small PR's -* Create tests for your new code, and for the old code too. We are aiming for a full test coverage. -* If your going to be working in old code that needs lots of reformatting consider doing the clean as a separate PR. -* If you can find others to help test your PR is will help get them merged in sooner. +* Join the [Discord chat][discord-url]. +* Get a local dev environment setup if you haven't already. +* Try breaking up big features/improvements into small parts. It's much easier to accept small PR's. +* Create tests for your new code as well as the old code. We are aiming for a full test coverage. +* If you're going to be working in old code that needs lots of reformatting, consider doing it as a separate PR. +* If you can find others to help test your PR, it will help get them merged in sooner. +## List of Contributors + +We welcome new contributors. We do not only need core contributors. Regular or one time contributors are welcomed as well. +Also if you can't code, it's possible to contribute by improving the documentation or by translating Nightscout in your own language + +### Core developers, contributing developers, coordinators and documentation writers + +[@andrew-warrington]: https://github.com/andrew-warrington +[@apanasef]: https://github.com/apanasef +[@bewest]: https://github.com/bewest +[@danamlewis]: https://github.com/danamlewis +[@diabetlum]: https://github.com/diabetlum +[@herzogmedia]: https://github.com/herzogmedia +[@jamieowendexcom ]: https://github.com/jamieowendexcom +[@janrpn]: https://github.com/janrpn +[@jasoncalabrese]: https://github.com/jasoncalabrese +[@jizhongwen]: https://github.com/jizhongwen +[@jpcunningh]: https://github.com/jpcunningh +[@jweismann]: https://github.com/jweismann +[@komarserjio]: https://github.com/komarserjio +[@LuminaryXion]: https://github.com/LuminaryXion +[@mcdafydd]: https://github.com/mcdafydd +[@mdomox]: https://github.com/mdomox +[@MilosKozak]: https://github.com/MilosKozak +[@oteroos]: https://github.com/oteroos +[@PieterGit]: https://github.com/PieterGit +[@rarneson]: https://github.com/rarneson +[@rickfriele]: https://github.com/rickfriele +[@scottleibrand]: https://github.com/scottleibrand +[@sulkaharo]: https://github.com/sulkaharo +[@tynbendad]: https://github.com/tynbendad +[@unsoluble]: https://github.com/unsoluble +[@viderehh]: https://github.com/viderehh +[@OpossumGit]: https://github.com/OpossumGit + +| Contribution area | List of contributors | +| ------------------------------------- | ---------------------------------- | +| Core developers: | [@jasoncalabrese] [@MilosKozak] [@PieterGit] [@sulkaharo] | +| Former Core developers: (not active): | [@bewest] | +| Contributing developers: | [@jpcunningh] [@scottleibrand] [@komarserjio] [@jweismann] | +| Release coordination 0.10.x: | [@PieterGit] [@sulkaharo] | +| Release coordination 0.11.x: | [@PieterGit] | +| Issue/Pull request coordination: | Please volunteer | +| Cleaning up git fork spam: | Please volunteer | +| Documentation writers: | [@andrew-warrington] [@unsoluble] [@tynbendad] [@danamlewis] [@rarneson] | + +### Plugin contributors + +| Contribution area | List of developers | List of testers +| ------------------------------------- | -------------------- | -------------------- | +| [`alexa` (Amazon Alexa)](README.md#alexa-amazon-alexa)| [@inventor96] | Please volunteer | +| [`ar2` (AR2 Forecasting)](README.md#ar2-ar2-forecasting)| Please volunteer | Please volunteer | +| [`basal` (Basal Profile)](README.md#basal-basal-profile)| Please volunteer | Please volunteer | +| [`boluscalc` (Bolus Wizard)](README.md#boluscalc-bolus-wizard)| Please volunteer | Please volunteer | +| [`bridge` (Share2Nightscout bridge)](README.md#bridge-share2nightscout-bridge)| Please volunteer | Please volunteer | +| [`bwp` (Bolus Wizard Preview)](README.md#bwp-bolus-wizard-preview)| Please volunteer | Please volunteer | +| [`cage` (Cannula Age)](README.md#cage-cannula-age)| [@jpcunningh] | Please volunteer | +| [`careportal` (Careportal)](README.md#careportal-careportal)| Please volunteer | Please volunteer | +| [`cob` (Carbs-on-Board)](README.md#cob-carbs-on-board)| Please volunteer | Please volunteer | +| [`cors` (CORS)](README.md#cors-cors)| Please volunteer | Please volunteer | +| [`delta` (BG Delta)](README.md#delta-bg-delta)| Please volunteer | Please volunteer | +| [`devicestatus` (Device Status)](README.md#devicestatus-device-status)| Please volunteer | Please volunteer | +| [`direction` (BG Direction)](README.md#direction-bg-direction)| Please volunteer | Please volunteer | +| [`errorcodes` (CGM Error Codes)](README.md#errorcodes-cgm-error-codes)| Please volunteer | Please volunteer | +| [`food` (Custom Foods)](README.md#food-custom-foods)| Please volunteer | Please volunteer | +| [`googlehome` (Google Home/DialogFlow)](README.md#googlehome-google-homedialogflow)| [@mdomox] [@rickfriele] [@inventor96] | [@mcdafydd] [@oteroos] [@jamieowendexcom] | +| [`iage` (Insulin Age)](README.md#iage-insulin-age)| Please volunteer | Please volunteer | +| [`iob` (Insulin-on-Board)](README.md#iob-insulin-on-board)| Please volunteer | Please volunteer | +| [`loop` (Loop)](README.md#loop-loop)| Please volunteer | Please volunteer | +| [`mmconnect` (MiniMed Connect bridge)](README.md#mmconnect-minimed-connect-bridge)| Please volunteer | Please volunteer | +| [`openaps` (OpenAPS)](README.md#openaps-openaps)| Please volunteer | Please volunteer | +| [`profile` (Treatment Profile)](README.md#profile-treatment-profile)| Please volunteer | Please volunteer | +| [`pump` (Pump Monitoring)](README.md#pump-pump-monitoring)| Please volunteer | Please volunteer | +| [`rawbg` (Raw BG)](README.md#rawbg-raw-bg)| [@jpcunningh] | Please volunteer | +| [`sage` (Sensor Age)](README.md#sage-sensor-age)| [@jpcunningh] | Please volunteer | +| [`simplealarms` (Simple BG Alarms)](README.md#simplealarms-simple-bg-alarms)| Please volunteer | Please volunteer | +| [`speech` (Speech)](README.md#speech-speech)| [@sulkaharo] | Please volunteer | +| [`timeago` (Time Ago)](README.md#timeago-time-ago)| Please volunteer | Please volunteer | +| [`treatmentnotify` (Treatment Notifications)](README.md#treatmentnotify-treatment-notifications)| Please volunteer | Please volunteer | +| [`upbat` (Uploader Battery)](README.md#upbat-uploader-battery)| [@jpcunningh] | Please volunteer | +| [`xdrip-js` (xDrip-js)](README.md#xdrip-js-xdrip-js)| [@jpcunningh] | Please volunteer | + +### Translators + +See `/translations` of your Nightscout, to view the current translation coverage and the missing items. +Languages with less than 90% coverage will be removed in a future Nightscout versions. + +| Language | List of translators | Status +| ------------- | -------------------- |-------------------- | +| Български (`bg`) |Please volunteer| OK | +| Čeština (`cs`) |Please volunteer|OK | +| Deutsch (`de`) |[@viderehh] [@herzogmedia] |OK | +| Dansk (`dk`) | [@janrpn] |OK | +| Ελληνικά (`el`)|Please volunteer|Needs attention: 68.5%| +| English (`en`)|Please volunteer|OK| +| Español (`es`) |Please volunteer|OK| +| Suomi (`fi`)|[@sulkaharo] |OK| +| Français (`fr`)|Please volunteer|OK| +| עברית (`he`)| [@jakebloom] |OK| +| Hrvatski (`hr`)|[@OpossumGit]|OK| +| Italiano (`it`)|Please volunteer|OK| +| 日本語 (`ja`)|[@LuminaryXion]|Working on this| +| 한국어 (`ko`)|Please volunteer|Needs attention: 80.6%| +| Norsk (Bokmål) (`nb`)|Please volunteer|OK| +| Nederlands (`nl`)|[@PieterGit]|OK| +| Polski (`pl`)|Please volunteer|OK| +| Português (Brasil) (`pt`)|Please volunteer|OK| +| Română (`ro`)|Please volunteer|OK| +| Русский (`ru`)|[@apanasef]|OK| +| Slovenčina (`sk`)|Please volunteer|OK| +| Svenska (`sv`)|Please volunteer|OK| +| Türkçe (`tr`)|[@diabetlum]|OK| +| 中文(简体) (`zh_cn`) | [@jizhongwen]|OK| +| 中文(繁體) (`zh_tw`) | [@jizhongwen]|Needs attention: 25.0% +| 日本語 (`ja_jp`) | [@LuminaryXion]| + + +### List of all contributors +| Contribution area | List of contributors | +| ------------------------------------- | -------------------- | +| All active developers: | [@jasoncalabrese] [@jpcunningh] [@jweismann] [@komarserjio] [@mdomox] [@MilosKozak] [@PieterGit] [@rickfriele] [@sulkaharo] [@unsoluble] +| All active testers/documentors: | [@danamlewis] [@jamieowendexcom] [@mcdafydd] [@oteroos] [@rarneson] [@tynbendad] [@unsoluble] +| All active translators: | [@apanasef] [@jizhongwen] [@viderehh] [@herzogmedia] [@LuminaryXion] [@OpossumGit] + diff --git a/Dockerfile.example b/Dockerfile.example index d4c13c0ecd9..89a43f43c15 100644 --- a/Dockerfile.example +++ b/Dockerfile.example @@ -1,17 +1,17 @@ -FROM node:8.9.1 +FROM node:10-alpine MAINTAINER Nightscout Contributors -RUN apt-get update && \ - apt-get -y dist-upgrade - RUN mkdir -p /opt/app ADD . /opt/app WORKDIR /opt/app +RUN chown -R node:node /opt/app +USER node RUN npm install && \ npm run postinstall && \ - npm run env + npm run env && \ + npm audit fix EXPOSE 1337 diff --git a/Makefile b/Makefile index 7829258bd58..1ca626ab88c 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,8 @@ MOCHA=./node_modules/mocha/bin/_mocha # Pinned from dependency list. ISTANBUL=./node_modules/.bin/istanbul ANALYZED=./coverage/lcov.info -export CODACY_REPO_TOKEN=e29ae5cf671f4f918912d9864316207c +# Following token deprecated +# export CODACY_REPO_TOKEN=e29ae5cf671f4f918912d9864316207c DOCKER_IMAGE=nightscout/cgm-remote-monitor-travis @@ -42,7 +43,7 @@ report: test_onebyone: python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);' - $(foreach var,$(wildcard tests/*.js),${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap $(var);) + for var in tests/*.js; do ${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap $$var; done | tap-set-exit test: ${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap ${TESTS} @@ -51,7 +52,7 @@ travis: python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);' # NODE_ENV=test ${MONGO_SETTINGS} \ # ${ISTANBUL} cover ${MOCHA} --report lcovonly -- --timeout 5000 -R tap ${TESTS} - $(foreach var,$(wildcard tests/*.js),${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap $(var);) + for var in tests/*.js; do ${MONGO_SETTINGS} ${MOCHA} --timeout 30000 --exit --bail -R tap $$var; done docker_release: # Get the version from the package.json file diff --git a/README.md b/README.md index 93dd92d17fd..f4a0452dd9d 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ Nightscout Web Monitor (a.k.a. cgm-remote-monitor) [![Dependency Status][dependency-img]][dependency-url] [![Coverage Status][coverage-img]][coverage-url] [![Codacy Badge][codacy-img]][codacy-url] -[![Gitter chat][gitter-img]][gitter-url] +[![Discord chat][discord-img]][discord-url] -[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/) [![Deploy to Heroku][heroku-img]][heroku-url] +[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/) [![Deploy to Heroku][heroku-img]][heroku-url] [![Update your site][update-img]][update-fork] This acts as a web-based CGM (Continuous Glucose Monitor) to allow multiple caregivers to remotely view a patient's glucose data in @@ -35,10 +35,12 @@ Community maintained fork of the [coverage-url]: https://coveralls.io/github/nightscout/cgm-remote-monitor?branch=master [codacy-img]: https://www.codacy.com/project/badge/f79327216860472dad9afda07de39d3b [codacy-url]: https://www.codacy.com/app/Nightscout/cgm-remote-monitor -[gitter-img]: https://img.shields.io/badge/Gitter-Join%20Chat%20%E2%86%92-1dce73.svg -[gitter-url]: https://gitter.im/nightscout/public +[discord-img]: https://img.shields.io/discord/629952586895851530?label=discord%20chat +[discord-url]: https://discord.gg/rTKhrqz [heroku-img]: https://www.herokucdn.com/deploy/button.png [heroku-url]: https://heroku.com/deploy +[update-img]: update.png +[update-fork]: http://nightscout.github.io/pages/update-fork/ [original]: https://github.com/rnpenguin/cgm-remote-monitor @@ -46,6 +48,11 @@ Community maintained fork of the **Table of Contents** - [Install](#install) + - [Supported configurations:](#supported-configurations) + - [Minimum browser requirements for viewing the site:](#minimum-browser-requirements-for-viewing-the-site) + - [Windows installation software requirements:](#windows-installation-software-requirements) + - [Installation notes for users with nginx or Apache reverse proxy for SSL/TLS offloading:](#installation-notes-for-users-with-nginx-or-apache-reverse-proxy-for-ssltls-offloading) + - [Installation notes for Microsoft Azure, Windows:](#installation-notes-for-microsoft-azure-windows) - [Usage](#usage) - [Updating my version?](#updating-my-version) - [What is my mongo string?](#what-is-my-mongo-string) @@ -58,7 +65,7 @@ Community maintained fork of the - [Alarms](#alarms) - [Core](#core) - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) - - [Views](#views) + - [Predefined values for your server settings (optional)](#predefined-values-for-your-server-settings-optional) - [Plugins](#plugins) - [Default Plugins](#default-plugins) - [`delta` (BG Delta)](#delta-bg-delta) @@ -70,7 +77,7 @@ Community maintained fork of the - [`ar2` (AR2 Forecasting)](#ar2-ar2-forecasting) - [`simplealarms` (Simple BG Alarms)](#simplealarms-simple-bg-alarms) - [`profile` (Treatment Profile)](#profile-treatment-profile) - - [Advanced Plugins](#advanced-plugins) + - [Advanced Plugins:](#advanced-plugins) - [`careportal` (Careportal)](#careportal-careportal) - [`boluscalc` (Bolus Wizard)](#boluscalc-bolus-wizard) - [`food` (Custom Foods)](#food-custom-foods) @@ -81,6 +88,7 @@ Community maintained fork of the - [`cage` (Cannula Age)](#cage-cannula-age) - [`sage` (Sensor Age)](#sage-sensor-age) - [`iage` (Insulin Age)](#iage-insulin-age) + - [`bage` (Battery Age)](#bage-battery-age) - [`treatmentnotify` (Treatment Notifications)](#treatmentnotify-treatment-notifications) - [`basal` (Basal Profile)](#basal-basal-profile) - [`bridge` (Share2Nightscout bridge)](#bridge-share2nightscout-bridge) @@ -88,8 +96,11 @@ Community maintained fork of the - [`pump` (Pump Monitoring)](#pump-pump-monitoring) - [`openaps` (OpenAPS)](#openaps-openaps) - [`loop` (Loop)](#loop-loop) - - [`xdrip-js` (xDrip-js)](#xdrip-js-xdrip-js) + - [`override` (Override Mode)](#override-override-mode) + - [`xdripjs` (xDrip-js)](#xdripjs-xdripjs) - [`alexa` (Amazon Alexa)](#alexa-amazon-alexa) + - [`googlehome` (Google Home/DialogFLow)](#googlehome-google-homedialogflow) + - [`speech` (Speech)](#speech-speech) - [`cors` (CORS)](#cors-cors) - [Extended Settings](#extended-settings) - [Pushover](#pushover) @@ -104,19 +115,34 @@ Community maintained fork of the # Install -Supported configurations: +## Supported configurations: -If you plan to use Nightscout, we recommend using [Heroku](http://openaps.readthedocs.io/en/latest/docs/While%20You%20Wait%20For%20Gear/nightscout-setup.html#nightscout-setup-with-heroku), as Nightscout can reach the usage limits of the free Azure plan and cause it to shut down for hours or days. If you end up needing a paid tier, the $7/mo Heroku plan is also much cheaper than the first paid tier of Azure. Currently, the only added benefit to choosing the $7/mo Heroku plan vs the free Heroku plan is a section showing site use metrics for performance (such as response time). This has limited benefit to the average Nightscout user. In short, Heroku is the free and best option for Nightscout hosting. +If you plan to use Nightscout, we recommend using [Heroku](http://www.nightscout.info/wiki/welcome/set-up-nightscout-using-heroku), as Nightscout can reach the usage limits of the free Azure plan and cause it to shut down for hours or days. If you end up needing a paid tier, the $7/mo Heroku plan is also much cheaper than the first paid tier of Azure. Currently, the only added benefit to choosing the $7/mo Heroku plan vs the free Heroku plan is a section showing site use metrics for performance (such as response time). This has limited benefit to the average Nightscout user. In short, Heroku is the free and best option for Nightscout hosting. -- [Nightscout Setup with Heroku] (http://openaps.readthedocs.io/en/latest/docs/While%20You%20Wait%20For%20Gear/nightscout-setup.html#nightscout-setup-with-heroku) (recommended) -- [Nightscout Setup with Microsoft Azure] (http://www.nightscout.info/wiki/faqs-2/azure-2) (not recommended, please +- [Nightscout Setup with Heroku](http://www.nightscout.info/wiki/welcome/set-up-nightscout-using-heroku) (recommended) +- [Nightscout Setup with Microsoft Azure](http://www.nightscout.info/wiki/faqs-2/azure-2) (not recommended, please [switch from Azure to Heroku](http://openaps.readthedocs.io/en/latest/docs/While%20You%20Wait%20For%20Gear/nightscout-setup.html#switching-from-azure-to-heroku) ) - Linux based install (Debian, Ubuntu, Raspbian) install with own Node.JS and MongoDB install (see software requirements below) - Windows based install with own Node.JS and MongoDB install (see software requirements below) -Software requirements: +## Recommended minimum browser versions for using Nightscout: + +Older versions of the browsers might work, but are untested. + +- Android 4 +- iOS 6 +- Chrome 35 +- Edge 17 +- Firefox 61 +- Opera 12.1 +- Safari 6 (macOS 10.7) +- Internet Explorer: not supported + +Some features may not work with devices/browsers on the older end of these requirements. -- [Node.js](http://nodejs.org/) Latest Node 8 LTS (Node 8.11.3 or later). Use [Install instructions for Node](https://nodejs.org/en/download/package-manager/) or use `setup.sh`) +## Windows installation software requirements: + +- [Node.js](http://nodejs.org/) Latest Node 8 LTS (Node 8.15.1 or later) or Node 10 LTS (Node 10.16.0 or later; Node 10.15.2 works for Azure). Node versions that do not have the latest security patches will not work. Use [Install instructions for Node](https://nodejs.org/en/download/package-manager/) or use `setup.sh`) - [MongoDB](https://www.mongodb.com/download-center?jmp=nav#community) 3.x or later. MongoDB 2.4 is only supported for Raspberry Pi. As a non-root user clone this repo then install dependencies into the root of the project: @@ -125,55 +151,59 @@ As a non-root user clone this repo then install dependencies into the root of th $ npm install ``` -Installation notes for Microsoft Azure, Windows and Node 10: +## Installation notes for users with nginx or Apache reverse proxy for SSL/TLS offloading: + +- Your site redirects insecure connections to `https` by default. If you use a reverse proxy like nginx or Apache to handle the connection security for you, make sure it sets the `X-Forwarded-Proto` header. Otherwise nightscout will be unable to know if it was called through a secure connection and will try to redirect you to the https version. If you're unable to set this Header, you can change the `INSECURE_USE_HTTP` setting in nightscout to true in order to allow insecure connections without being redirected. +- In case you use a proxy. Do not use an external network interfaces for hosting Nightscout. Make sure the unsecure port is not available from a remote network connection +- HTTP Strict Transport Security (HSTS) headers are enabled by default, use settings `SECURE_HSTS_HEADER` and `SECURE_HSTS_HEADER_*` +- See [Predefined values for your server settings](#predefined-values-for-your-server-settings-optional) for more details -- If deploying the software to Microsoft Azure, you must set ** in the app settings for *WEBSITE_NODE_DEFAULT_VERSION* and *SCM_COMMAND_IDLE_TIMEOUT* **before** you deploy the latest Nightscout or the site deployment will likely fail. Other hosting environments do not require this setting. Please use: +## Installation notes for Microsoft Azure, Windows: + +- If deploying the software to Microsoft Azure, you must set ** in the app settings for *WEBSITE_NODE_DEFAULT_VERSION* and *SCM_COMMAND_IDLE_TIMEOUT* **before** you deploy the latest Nightscout or the site deployment will likely fail. Other hosting environments do not require this setting. Additionally, if using the Azure free hosting tier, the installation might fail due to resource constraints imposed by Azure on the free hosting. Please set the following settings to the environment in Azure: ``` -WEBSITE_NODE_DEFAULT_VERSION=8.11.1 +WEBSITE_NODE_DEFAULT_VERSION=10.15.2 SCM_COMMAND_IDLE_TIMEOUT=300 ``` - See [install MongoDB, Node.js, and Nightscouton a single Windows system](https://github.com/jaylagorio/Nightscout-on-Windows-Server). if you want to host your Nightscout outside of the cloud. Although the instructions are intended for Windows Server the procedure is compatible with client versions of Windows such as Windows 7 and Windows 10. -- If you deploy to Windows and want to develop or test you need to install [Cygwin] (https://www.cygwin.com/) (use [setup-x86_64.exe] (https://www.cygwin.com/setup-x86_64.exe) and make sure to install `build-essential` package. Test your configuration by executing `make` and check if all tests are ok. -- There may be some issues with Node 10.6.0 or later with Nightscout. Node 10 support will be in the 0.11 release. Please don't use Nightscout with (Node 9 or) Node 10 at this moment. +- If you deploy to Windows and want to develop or test you need to install [Cygwin](https://www.cygwin.com/) (use [setup-x86_64.exe](https://www.cygwin.com/setup-x86_64.exe) and make sure to install `build-essential` package. Test your configuration by executing `make` and check if all tests are ok. + +# Development + +Want to help with development, or just see how Nightscout works? Great! See [CONTRIBUTING.md](CONTRIBUTING.md) for development-related documentation. # Usage The data being uploaded from the server to the client is from a -MongoDB server such as [mongolab][mongodb]. +MongoDB server such as [mLab][mLab]. -[mongodb]: https://mongolab.com +[mLab]: https://mlab.com/ [autoconfigure]: https://nightscout.github.io/pages/configure/ [mongostring]: https://nightscout.github.io/pages/mongostring/ -[update-fork]: http://nightscout.github.io/pages/update-fork/ ## Updating my version? -The easiest way to update your version of cgm-remote-monitor to our latest -recommended version is to use the [update my fork tool][update-fork]. It even -gives out stars if you are up to date. -## What is my mongo string? - -Try the [what is my mongo string tool][mongostring] to get a good idea of your -mongo string. You can copy and paste the text in the gray box into your -`MONGO_CONNECTION` environment variable. +The easiest way to update your version of cgm-remote-monitor to the latest version is to use the [update tool][update-fork]. A step-by-step guide is available [here][http://www.nightscout.info/wiki/welcome/how-to-update-to-latest-cgm-remote-monitor-aka-cookie]. +To downgrade to an older version, follow [this guide][http://www.nightscout.info/wiki/welcome/how-to-deploy-an-older-version-of-nightscout]. ## Configure my uploader to match Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. - ## Nightscout API -The Nightscout API enables direct access to your DData without the need for direct Mongo access. +The Nightscout API enables direct access to your data without the need for Mongo access. You can find CGM data in `/api/v1/entries`, Care Portal Treatments in `/api/v1/treatments`, and Treatment Profiles in `/api/v1/profile`. The server status and settings are available from `/api/v1/status.json`. By default the `/entries` and `/treatments` APIs limit results to the the most recent 10 values from the last 2 days. You can get many more results, by using the `count`, `date`, `dateString`, and `created_at` parameters, depending on the type of data you're looking for. +Once you've installed Nightscout, you can access API documentation by loading `/api-docs/` URL in your instance. + #### Example Queries -(replace `http://localhost:1337` with your base url, YOUR-SITE) +(replace `http://localhost:1337` with your own URL) * 100's: `http://localhost:1337/api/v1/entries.json?find[sgv]=100` * Count of 100's in a month: `http://localhost:1337/api/v1/count/entries/where?find[dateString][$gte]=2016-09&find[dateString][$lte]=2016-10&find[sgv]=100` @@ -182,8 +212,7 @@ You can get many more results, by using the `count`, `date`, `dateString`, and ` * Boluses over 2U: `http://localhost:1337/api/v1/treatments.json?find[insulin][$gte]=2` The API is Swagger enabled, so you can generate client code to make working with the API easy. -To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.html or review [swagger.yaml](swagger.yaml). - +To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or review [swagger.yaml](swagger.yaml). ## Environment @@ -191,31 +220,31 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm ### Required - * `MONGO_CONNECTION` - Your mongo uri, for example: `mongodb://sally:sallypass@ds099999.mongolab.com:99999/nightscout` - * `DISPLAY_UNITS` (`mg/dl`) - Choices: `mg/dl` and `mmol`. Setting to `mmol` puts the entire server into `mmol` mode by default, no further settings needed. - * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http + * `MONGODB_URI` - The connection string for your Mongo database. Something like `mongodb://sally:sallypass@ds099999.mongolab.com:99999/nightscout`. + * `API_SECRET` - A secret passphrase that must be at least 12 characters long. + * `MONGODB_COLLECTION` (`entries`) - The Mongo collection where CGM entries are stored. + * `DISPLAY_UNITS` (`mg/dl`) - Options are `mg/dl` or `mmol/L` (or just `mmol`). Setting to `mmol/L` puts the entire server into `mmol/L` mode by default, no further settings needed. -### Features/Labs +### Features * `ENABLE` - Used to enable optional features, expects a space delimited list, such as: `careportal rawbg iob`, see [plugins](#plugins) below * `DISABLE` - Used to disable default features, expects a space delimited list, such as: `direction upbat`, see [plugins](#plugins) below - * `API_SECRET` - A secret passphrase that must be at least 12 characters long, required to enable `POST` and `PUT`; also required for the Care Portal + * `BASE_URL` - Used for building links to your site's API, i.e. Pushover callbacks, usually the URL of your Nightscout site. * `AUTH_DEFAULT_ROLES` (`readable`) - possible values `readable`, `denied`, or any valid role name. When `readable`, anyone can view Nightscout without a token. Setting it to `denied` will require a token from every visit, using `status-only` will enable api-secret based login. * `IMPORT_CONFIG` - Used to import settings and extended settings from a url such as a gist. Structure of file should be something like: `{"settings": {"theme": "colors"}, "extendedSettings": {"upbat": {"enableAlerts": true}}}` * `TREATMENTS_AUTH` (`on`) - possible values `on` or `off`. Deprecated, if set to `off` the `careportal` role will be added to `AUTH_DEFAULT_ROLES` - ### Alarms - These alarm setting effect all delivery methods (browser, pushover, maker, etc), some settings can be overridden per client (web browser) + These alarm setting affect all delivery methods (browser, Pushover, IFTTT, etc.). Values and settings entered here will be the defaults for new browser views, but will be overridden if different choices are made in the settings UI. * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's - * `BG_HIGH` (`260`) - must be set using mg/dl units; the high BG outside the target range that is considered urgent - * `BG_TARGET_TOP` (`180`) - must be set using mg/dl units; the top of the target range, also used to draw the line on the chart - * `BG_TARGET_BOTTOM` (`80`) - must be set using mg/dl units; the bottom of the target range, also used to draw the line on the chart - * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent + * `BG_HIGH` (`260`) - the high BG outside the target range that is considered urgent (interprets units based on DISPLAY_UNITS setting) + * `BG_TARGET_TOP` (`180`) - the top of the target range, also used to draw the line on the chart (interprets units based on DISPLAY_UNITS setting) + * `BG_TARGET_BOTTOM` (`80`) - the bottom of the target range, also used to draw the line on the chart (interprets units based on DISPLAY_UNITS setting) + * `BG_LOW` (`55`) - the low BG outside the target range that is considered urgent (interprets units based on DISPLAY_UNITS setting) * `ALARM_URGENT_HIGH` (`on`) - possible values `on` or `off` * `ALARM_URGENT_HIGH_MINS` (`30 60 90 120`) - Number of minutes to snooze urgent high alarms, space separated for options in browser, first used for pushover * `ALARM_HIGH` (`on`) - possible values `on` or `off` @@ -227,10 +256,8 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `ALARM_URGENT_MINS` (`30 60 90 120`) - Number of minutes to snooze urgent alarms (that aren't tagged as high or low), space separated for options in browser, first used for pushover * `ALARM_WARN_MINS` (`30 60 90 120`) - Number of minutes to snooze warning alarms (that aren't tagged as high or low), space separated for options in browser, first used for pushover - ### Core - * `MONGO_COLLECTION` (`entries`) - The collection used to store SGV, MBG, and CAL records from your CGM device * `MONGO_TREATMENTS_COLLECTION` (`treatments`) -The collection used to store treatments entered in the Care Portal, see the `ENABLE` env var above * `MONGO_DEVICESTATUS_COLLECTION`(`devicestatus`) - The collection used to store device status information such as uploader battery * `MONGO_PROFILE_COLLECTION`(`profile`) - The collection used to store your profiles @@ -238,19 +265,20 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `MONGO_ACTIVITY_COLLECTION`(`activity`) - The collection used to store activity data * `PORT` (`1337`) - The port that the node.js application will listen on. * `HOSTNAME` - The hostname that the node.js application will listen on, null by default for any hostname for IPv6 you may need to use `::`. - * `SSL_KEY` - Path to your ssl key file, so that ssl(https) can be enabled directly in node.js - * `SSL_CERT` - Path to your ssl cert file, so that ssl(https) can be enabled directly in node.js - * `SSL_CA` - Path to your ssl ca file, so that ssl(https) can be enabled directly in node.js + * `SSL_KEY` - Path to your ssl key file, so that ssl(https) can be enabled directly in node.js. If using Let's Encrypt, make this variable the path to your privkey.pem file (private key). + * `SSL_CERT` - Path to your ssl cert file, so that ssl(https) can be enabled directly in node.js. If using Let's Encrypt, make this variable the path to fullchain.pem file (cert + ca). + * `SSL_CA` - Path to your ssl ca file, so that ssl(https) can be enabled directly in node.js. If using Let's Encrypt, make this variable the path to chain.pem file (chain). * `HEARTBEAT` (`60`) - Number of seconds to wait in between database checks * `DEBUG_MINIFY` (`true`) - Debug option, setting to `false` will disable bundle minification to help tracking down error and speed up development - + * `DE_NORMALIZE_DATES`(`true`) - The Nightscout REST API normalizes all entered dates to UTC zone. Some Nightscout clients have broken date deserialization logic and expect to received back dates in zoned formats. Setting this variable to `true` causes the REST API to serialize dates sent to Nightscout in zoned format back to zoned format when served to clients over REST. ### Predefined values for your browser settings (optional) + * `TIME_FORMAT` (`12`)- possible values `12` or `24` * `NIGHT_MODE` (`off`) - possible values `on` or `off` * `SHOW_RAWBG` (`never`) - possible values `always`, `never` or `noise` - * `CUSTOM_TITLE` (`Nightscout`) - Usually name of T1 - * `THEME` (`default`) - possible values `default`, `colors`, or `colorblindfriendly` + * `CUSTOM_TITLE` (`Nightscout`) - Title for the main view + * `THEME` (`colors`) - possible values `default`, `colors`, or `colorblindfriendly` * `ALARM_TIMEAGO_WARN` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_WARN_MINS` (`15`) - minutes since the last reading to trigger a warning * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` @@ -258,19 +286,27 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `SHOW_PLUGINS` - enabled plugins that should have their visualizations shown, defaults to all enabled * `SHOW_FORECAST` (`ar2`) - plugin forecasts that should be shown by default, supports space delimited values such as `"ar2 openaps"` * `LANGUAGE` (`en`) - language of Nightscout. If not available english is used - * Currently supported language codes are: bg (Български), cs (Čeština), de (Deutsch), dk (Dansk), el (Ελληνικά), en (English), es (Español), fi (Suomi), fr (Français), he (עברית), hr (Hrvatski), it (Italiano), ko (한국어), nb (Norsk (Bokmål)), nl (Nederlands), pl (Polski), pt (Português (Brasil)), ro (Română), ru (Русский), sk (Slovenčina), sv (Svenska), zh_cn (中文(简体)), zh_tw (中文(繁體)) + * Currently supported language codes are: bg (Български), cs (Čeština), de (Deutsch), dk (Dansk), el (Ελληνικά), en (English), es (Español), fi (Suomi), fr (Français), he (עברית), hr (Hrvatski), it (Italiano), ko (한국어), nb (Norsk (Bokmål)), nl (Nederlands), pl (Polski), pt (Português (Brasil)), ro (Română), ru (Русский), sk (Slovenčina), sv (Svenska), tr (Turkish), zh_cn (中文(简体)), zh_tw (中文(繁體)) * `SCALE_Y` (`log`) - The type of scaling used for the Y axis of the charts system wide. * The default `log` (logarithmic) option will let you see more detail towards the lower range, while still showing the full CGM range. - * The `linear` option has equidistant tick marks, the range used is dynamic so that space at the top of chart isn't wasted. + * The `linear` option has equidistant tick marks; the range used is dynamic so that space at the top of chart isn't wasted. * The `log-dynamic` is similar to the default `log` options, but uses the same dynamic range and the `linear` scale. - * `EDIT_MODE` (`on`) - possible values `on` or `off`. Enable or disable icon allowing enter treatments edit mode + * `EDIT_MODE` (`on`) - possible values `on` or `off`. Enables the icon allowing for editing of treatments in the main view. + +### Predefined values for your server settings (optional) + * `INSECURE_USE_HTTP` (`false`) - Redirect unsafe http traffic to https. Possible values `false`, or `true`. Your site redirects to `https` by default. If you don't want that from Nightscout, but want to implement that with a Nginx or Apache proxy, set `INSECURE_USE_HTTP` to `true`. Note: This will allow (unsafe) http traffic to your Nightscout instance and is not recommended. + * `SECURE_HSTS_HEADER` (`true`) - Add HTTP Strict Transport Security (HSTS) header. Possible values `false`, or `true`. + * `SECURE_HSTS_HEADER_INCLUDESUBDOMAINS` (`false`) - includeSubdomains options for HSTS. Possible values `false`, or `true`. + * `SECURE_HSTS_HEADER_PRELOAD` (`false`) - ask for preload in browsers for HSTS. Possible values `false`, or `true`. + * `SECURE_CSP` (`false`) - Add Content Security Policy headers. Possible values `false`, or `true`. + * `SECURE_CSP_REPORT_ONLY` (`false`) - If set to `true` allows to experiment with policies by monitoring (but not enforcing) their effects. Possible values `false`, or `true`. ### Views - There are a few alternate web views available that display a simplified BG stream. Append any of these to your Nightscout URL: - * `/clock.html` - Shows current BG. Grey text on a black background. - * `/bgclock.html` - Shows current BG, trend arrow, and time of day. Grey text on a black background. - * `/clock-color.html` - 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). + 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.) + * `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. + * `Simple` - Shows current BG. Grey text on a black background. ### Plugins @@ -280,7 +316,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm #### Default Plugins - These can be disabled by setting the `DISABLE` env var, for example `DISABLE="direction upbat"` + These can be disabled by adding them to the `DISABLE` variable, for example `DISABLE="direction upbat"` ##### `delta` (BG Delta) Calculates and displays the change between the last 2 BG values. @@ -302,7 +338,6 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm - ##### `devicestatus` (Device Status) Used by `upbat` and other plugins to display device status info. Supports the `DEVICESTATUS_ADVANCED="true"` [extended setting](#extended-settings) to send all device statuses to the client for retrospective use and to support other plugins. @@ -339,7 +374,11 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm An option plugin to enable adding foods from database in Bolus Wizard and enable . ##### `rawbg` (Raw BG) - Calculates BG using sensor and calibration records from and displays an alternate BG values and noise levels. + Calculates BG using sensor and calibration records from and displays an alternate BG values and noise levels. Defaults that can be adjusted with [extended setting](#extended-settings) + * `DISPLAY` (`unsmoothed`) - Allows the user to control which algorithm is used to calculate the displayed raw BG values using the most recent calibration record. + * `unfiltered` - Raw BG is calculated by applying the calibration to the glucose record's unfiltered value. + * `filtered` - Raw BG is calculated by applying the calibration to the glucose record's filtered value. The glucose record's filtered values are generally produced by the CGM by a running average of the unfiltered values to produce a smoothed value when the sensor noise is high. + * `unsmoothed` - Raw BG is calculated by first finding the ratio of the calculated filtered value (the same value calculated by the `filtered` setting) to the reported glucose value. The displayed raw BG value is calculated by dividing the calculated unfiltered value (the same value calculated by the `unfiltered` setting) by the ratio. The effect is to exagerate changes in trend direction so the trend changes are more noticeable to the user. This is the legacy raw BG calculation algorithm. ##### `iob` (Insulin-on-Board) Adds the IOB pill visualization in the client and calculates values that used by other plugins. Uses treatments with insulin doses and the `dia` and `sens` fields from the [treatment profile](#treatment-profile). @@ -376,6 +415,14 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `IAGE_WARN` (`48`) - If time since last `Insulin Change` matches `IAGE_WARN`, user will be alarmed to to change the insulin reservoir * `IAGE_URGENT` (`72`) - If time since last `Insulin Change` matches `IAGE_URGENT`, user will be issued a persistent warning of overdue change. +##### `bage` (Battery Age) + Calculates the number of days and hours since the last `Pump Battery Change` treatment that was recorded. + * `BAGE_ENABLE_ALERTS` (`false`) - Set to `true` to enable notifications to remind you of upcoming pump battery change. + * `BAGE_DISPLAY` (`days`) - Set to `hours` to display time since last `Pump Battery Change` in hours only. + * `BAGE_INFO` (`312`) - If time since last `Pump Battery Change` matches `BAGE_INFO` hours, user will be warned of upcoming pump battery change (default of 312 hours is 13 days). + * `BAGE_WARN` (`336`) - If time since last `Pump Battery Change` matches `BAGE_WARN` hours, user will be alarmed to to change the pump battery (default of 336 hours is 14 days). + * `BAGE_URGENT` (`360`) - If time since last `Pump Battery Change` matches `BAGE_URGENT` hours, user will be issued a persistent warning of overdue change (default of 360 hours is 15 days). + ##### `treatmentnotify` (Treatment Notifications) Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). @@ -388,7 +435,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `BRIDGE_USER_NAME` - Your user name for the Share service. * `BRIDGE_PASSWORD` - Your password for the Share service. * `BRIDGE_INTERVAL` (`150000` *2.5 minutes*) - The time to wait between each update. - * `BRIDGE_MAX_COUNT` (`1`) - The maximum number of records to fetch per update. + * `BRIDGE_MAX_COUNT` (`1`) - The number of records to attempt to fetch per update. * `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). @@ -402,6 +449,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `MMCONNECT_SGV_LIMIT` (`24`) - Maximum number of recent sensor glucose values to send to Nightscout on each request. * `MMCONNECT_VERBOSE` - Set this to "true" to log CareLink request information to the console. * `MMCONNECT_STORE_RAW_DATA` - Set this to "true" to store raw data returned from CareLink as `type: "carelink_raw"` database entries (useful for development). + * `MMCONNECT_SERVER` - Set this to `EU` if you're using the European Medtronic services ##### `pump` (Pump Monitoring) Generic Pump Monitoring for OpenAPS, MiniMed Connect, RileyLink, t:slim, with more on the way @@ -427,6 +475,12 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `OPENAPS_URGENT` (`60`) - The number of minutes since the last loop that needs to be exceed before an urgent alarm is triggered * `OPENAPS_FIELDS` (`status-symbol status-label iob meal-assist rssi`) - The fields to display by default. Any of the following fields: `status-symbol`, `status-label`, `iob`, `meal-assist`, `freq`, and `rssi` * `OPENAPS_RETRO_FIELDS` (`status-symbol status-label iob meal-assist rssi`) - The fields to display in retro mode. Any of the above fields. + * `OPENAPS_PRED_IOB_COLOR` (`#1e88e5`) - The color to use for IOB prediction lines. Colors can be in `#RRGGBB` format, but [other CSS color units](https://www.w3.org/TR/css-color-3/#colorunits) may be used as well. + * `OPENAPS_PRED_COB_COLOR` (`#FB8C00`) - The color to use for COB prediction lines. Same format as above. + * `OPENAPS_PRED_ACOB_COLOR` (`#FB8C00`) - The color to use for ACOB prediction lines. Same format as above. + * `OPENAPS_PRED_ZT_COLOR` (`#00d2d2`) - The color to use for ZT prediction lines. Same format as above. + * `OPENAPS_PRED_UAM_COLOR` (`#c9bd60`) - The color to use for UAM prediction lines. Same format as above. + * `OPENAPS_COLOR_PREDICTION_LINES` (`true`) - Enables / disables the colored lines vs the classic purple color. Also see [Pushover](#pushover) and [IFTTT Maker](#ifttt-maker). @@ -438,15 +492,28 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm * `LOOP_URGENT` (`60`) - The number of minutes since the last loop that needs to be exceeded before an urgent alarm is triggered * Add `loop` to `SHOW_FORECAST` to show forecasted BG. -##### `xdrip-js` (xDrip-js) +For remote overrides, the following extended settings must be configured: + * `LOOP_APNS_KEY` - Apple Push Notifications service (APNs) Key, created in the Apple Developer website. + * `LOOP_APNS_KEY_ID` - The Key ID for the above key. + * `LOOP_DEVELOPER_TEAM_ID` - Your Apple developer team ID. + * `LOOP_PUSH_SERVER_ENVIRONMENT` - (optional) Set this to `production` if you are using a provisioning profile that specifies production aps-environment, such as when distributing builds via TestFlight. + +##### `override` (Override Mode) + Additional monitoring for DIY automated insulin delivery systems to display real-time overrides such as Eating Soon or Exercise Mode: + * Requires `DEVICESTATUS_ADVANCED="true"` to be set + +##### `xdripjs` (xDrip-js) Integrated xDrip-js monitoring, uses these extended settings: * Requires `DEVICESTATUS_ADVANCED="true"` to be set - * `XDRIP-JS_ENABLE_ALERTS` (`false`) - Set to `true` to enable notifications when CGM state is not OK or battery voltages fall below threshold. - * `XDRIP-JS_STATE_NOTIFY_INTRVL` (`0.5`) - Set to number of hours between CGM state notifications - * `XDRIP-JS_WARN_BAT_V` (`300`) - The voltage of either transmitter battery, a warning will be triggered when dropping below this threshold. + * `XDRIPJS_ENABLE_ALERTS` (`false`) - Set to `true` to enable notifications when CGM state is not OK or battery voltages fall below threshold. + * `XDRIPJS_STATE_NOTIFY_INTRVL` (`0.5`) - Set to number of hours between CGM state notifications + * `XDRIPJS_WARN_BAT_V` (`300`) - The voltage of either transmitter battery, a warning will be triggered when dropping below this threshold. ##### `alexa` (Amazon Alexa) - Integration with Amazon Alexa, [detailed setup instructions](lib/plugins/alexa-plugin.md) + Integration with Amazon Alexa, [detailed setup instructions](docs/plugins/alexa-plugin.md) + +##### `googlehome` (Google Home/DialogFLow) + Integration with Google Home (via DialogFlow), [detailed setup instructions](docs/plugins/googlehome-plugin.md) ##### `speech` (Speech) Speech synthesis plugin. When enabled, speaks out the blood glucose values, IOB and alarms. Note you have to set the LANGUAGE setting on the server to get all translated alarms. @@ -459,7 +526,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm Some plugins support additional configuration using extra environment variables. These are prefixed with the name of the plugin and a `_`. For example setting `MYPLUGIN_EXAMPLE_VALUE=1234` would make `extendedSettings.exampleValue` available to the `MYPLUGIN` plugin. 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. #### Pushover @@ -490,19 +557,18 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm For testing/development try [localtunnel](http://localtunnel.me/). #### IFTTT Maker - In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). + In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Webhooks](https://ifttt.com/maker_webhooks). - With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. + With Maker you are able to integrate with all the other [IFTTT Services](https://ifttt.com/services). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) - 2. Find your secret key on the [maker page](https://ifttt.com/maker) - 3. Configure Nightscout by setting these environment variables: - * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. - * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` This also support a space delimited list of keys. - * `MAKER_ANNOUNCEMENT_KEY` - An optional Maker key, will be used for system wide user generated announcements. If not defined this will fallback to `MAKER_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. - 4. [Create a recipe](https://ifttt.com/myrecipes/personal/new) or see [more detailed instructions](lib/plugins/maker-setup.md#create-a-recipe) - - Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + 2. Follow the [Detailed IFTTT setup Instructions](docs/plugins/maker-setup.md) + 3. Configure Nightscout by setting these webpage environment variables: + * `ENABLE` - `maker` should be added to the list of plugins, for example: `ENABLE="maker"`. + * `MAKER_KEY` - Set this to your secret key (see [[Detailed Instructions](docs/plugins/maker-setup.md) ) `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` This also supports a space delimited list of keys. + * `MAKER_ANNOUNCEMENT_KEY` - An optional Maker key, will be used for system wide user generated announcements. If not defined this will fallback to `MAKER_KEY`. A possible use for this is sending important messages and alarms to another device that you don't want to send all notification too. This also support a space delimited list of keys. + + Plugins can create custom events, but all events sent to IFTTT webhooks will be prefixed with `ns-`. The core events are: * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. @@ -568,6 +634,12 @@ Feel free to [post an issue][issues], but read the [wiki][wiki] first. [issues]: https://github.com/nightscout/cgm-remote-monitor/issues [wiki]: https://github.com/nightscout/cgm-remote-monitor/wiki +### Browser testing suite provided by +[![BrowserStack][browserstack-img]][browserstack-url] + +[browserstack-img]: /static/images/browserstack-logo.png +[browserstack-url]: https://www.browserstack.com/ + License --------------- diff --git a/app.js b/app.js index 5fd5f85f254..7c6f67b1ee1 100644 --- a/app.js +++ b/app.js @@ -1,211 +1,321 @@ 'use strict'; -var _get = require('lodash/get'); -var express = require('express'); -var compression = require('compression'); -var bodyParser = require('body-parser'); -var prettyjson = require('prettyjson'); - -var path = require('path'); -var fs = require('fs'); - -function create(env, ctx) { - var app = express(); - var appInfo = env.name + ' ' + env.version; - app.set('title', appInfo); - app.enable('trust proxy'); // Allows req.secure test on heroku https connections. - - app.set('view engine', 'ejs'); - // this allows you to render .html files as templates in addition to .ejs - app.engine('html', require('ejs').renderFile); - app.engine('appcache', require('ejs').renderFile); - app.set("views", path.join(__dirname, "views/")); - - app.locals.cachebuster = fs.readFileSync(process.cwd() + '/tmp/cacheBusterToken').toString().trim(); - - if (ctx.bootErrors && ctx.bootErrors.length > 0) { - app.get('*', require('./lib/server/booterror')(ctx)); - return app; +const _get = require('lodash/get'); +const express = require('express'); +const compression = require('compression'); +const bodyParser = require('body-parser'); + +const path = require('path'); +const fs = require('fs'); + +function create (env, ctx) { + var app = express(); + var appInfo = env.name + ' ' + env.version; + app.set('title', appInfo); + app.enable('trust proxy'); // Allows req.secure test on heroku https connections. + var insecureUseHttp = env.insecureUseHttp; + var secureHstsHeader = env.secureHstsHeader; + if (!insecureUseHttp) { + console.info('Redirecting http traffic to https because INSECURE_USE_HTTP=', insecureUseHttp); + app.use((req, res, next) => { + if (req.header('x-forwarded-proto') === 'https' || req.secure) { + next(); + } else { + res.redirect(307, `https://${req.header('host')}${req.url}`); + } + }); + if (secureHstsHeader) { // Add HSTS (HTTP Strict Transport Security) header + console.info('Enabled SECURE_HSTS_HEADER (HTTP Strict Transport Security)'); + const helmet = require('helmet'); + var includeSubDomainsValue = env.secureHstsHeaderIncludeSubdomains; + var preloadValue = env.secureHstsHeaderPreload; + app.use(helmet({ + hsts: { + maxAge: 31536000 + , includeSubDomains: includeSubDomainsValue + , preload: preloadValue + } + , frameguard: false + })); + if (env.secureCsp) { + var secureCspReportOnly = env.secureCspReportOnly; + if (secureCspReportOnly) { + console.info('Enabled SECURE_CSP (Content Security Policy header). Not enforcing. Report only.'); + } else { + console.info('Enabled SECURE_CSP (Content Security Policy header). Enforcing.'); + } + app.use(helmet.contentSecurityPolicy({ //TODO make NS work without 'unsafe-inline' + directives: { + defaultSrc: ["'self'"] + , styleSrc: ["'self'", 'https://fonts.googleapis.com/', "'unsafe-inline'"] + , scriptSrc: ["'self'", "'unsafe-inline'"] + , fontSrc: ["'self'", 'https://fonts.gstatic.com/', 'data:'] + , imgSrc: ["'self'", 'data:'] + , objectSrc: ["'none'"], // Restricts , , and elements + reportUri: '/report-violation' + , frameAncestors: ["'none'"], // Clickjacking protection, using frame-ancestors + baseUri: ["'none'"], // Restricts use of the tag + formAction: ["'self'"], // Restricts where
contents may be submitted + } + , reportOnly: secureCspReportOnly + })); + app.use(helmet.referrerPolicy({ policy: 'no-referrer' })); + app.use(helmet.featurePolicy({ features: { payment: ["'none'"], } })); + app.use(bodyParser.json({ type: ['json', 'application/csp-report'] })); + app.post('/report-violation', (req, res) => { + if (req.body) { + console.log('CSP Violation: ', req.body) + } else { + console.log('CSP Violation: No data received!') + } + res.status(204).end() + }) + } } - - if (env.settings.isEnabled('cors')) { - var allowOrigin = _get(env, 'extendedSettings.cors.allowOrigin') || '*'; - console.info('Enabled CORS, allow-origin:', allowOrigin); - app.use(function allowCrossDomain(req, res, next) { - res.header('Access-Control-Allow-Origin', allowOrigin); - res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); - res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With'); - - // intercept OPTIONS method - if ('OPTIONS' === req.method) { - res.send(200); - } else { - next(); - } - }); + } else { + console.info('Security settings: INSECURE_USE_HTTP=', insecureUseHttp, ', SECURE_HSTS_HEADER=', secureHstsHeader); + } + + app.set('view engine', 'ejs'); + // this allows you to render .html files as templates in addition to .ejs + app.engine('html', require('ejs').renderFile); + app.engine('appcache', require('ejs').renderFile); + app.set("views", path.join(__dirname, "views/")); + + let cacheBuster = 'developmentMode'; + if (process.env.NODE_ENV !== 'development') { + if (fs.existsSync(process.cwd() + '/tmp/cacheBusterToken')) { + cacheBuster = fs.readFileSync(process.cwd() + '/tmp/cacheBusterToken').toString().trim(); + } else { + cacheBuster = fs.readFileSync(__dirname + '/tmp/cacheBusterToken').toString().trim(); } + } + app.locals.cachebuster = cacheBuster; - /////////////////////////////////////////////////// - // api and json object variables - /////////////////////////////////////////////////// - var api = require('./lib/api/')(env, ctx); - var ddata = require('./lib/data/endpoints')(env, ctx); - - app.use(compression({ - filter: function shouldCompress(req, res) { - //TODO: return false here if we find a condition where we don't want to compress - // fallback to standard filter function - return compression.filter(req, res); - } - })); - - app.get("/", (req, res) => { - res.render("index.html", { - locals: app.locals - }); - }); - - var appPages = { - "/clock-color.html":"clock-color.html", - "/admin":"adminindex.html", - "/profile":"profileindex.html", - "/food":"foodindex.html", - "/bgclock.html":"bgclock.html", - "/report":"reportindex.html", - "/translations":"translationsindex.html", - "/clock.html":"clock.html" - }; - - Object.keys(appPages).forEach(function(page) { - app.get(page, (req, res) => { - res.render(appPages[page], { - locals: app.locals - }); - }); - }); - - app.get("/nightscout.appcache", (req, res) => { - res.render("nightscout.appcache", { - locals: app.locals - }); + if (ctx.bootErrors && ctx.bootErrors.length > 0) { + app.get('*', require('./lib/server/booterror')(ctx)); + return app; + } + + if (env.settings.isEnabled('cors')) { + var allowOrigin = _get(env, 'extendedSettings.cors.allowOrigin') || '*'; + console.info('Enabled CORS, allow-origin:', allowOrigin); + app.use(function allowCrossDomain (req, res, next) { + res.header('Access-Control-Allow-Origin', allowOrigin); + res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); + res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With'); + + // intercept OPTIONS method + if ('OPTIONS' === req.method) { + res.send(200); + } else { + next(); + } }); + } + + /////////////////////////////////////////////////// + // api and json object variables + /////////////////////////////////////////////////// + const apiRoot = require('./lib/api/root')(env, ctx); + var api = require('./lib/api/')(env, ctx); + var api3 = require('./lib/api3/')(env, ctx); + var ddata = require('./lib/data/endpoints')(env, ctx); + var notificationsV2 = require('./lib/api/notifications-v2')(app, ctx); + + app.use(compression({ + filter: function shouldCompress (req, res) { + //TODO: return false here if we find a condition where we don't want to compress + // fallback to standard filter function + return compression.filter(req, res); + } + })); - app.use('/api/v1', bodyParser({ - limit: 1048576 * 50 - }), api); - - app.use('/api/v2/properties', ctx.properties); - app.use('/api/v2/authorization', ctx.authorization.endpoints); - app.use('/api/v2/ddata', ddata); + const clockviews = require('./lib/server/clocks.js')(env, ctx); + clockviews.setLocals(app.locals); - // pebble data - app.get('/pebble', ctx.pebble); + app.use("/clock", clockviews); - // expose swagger.json - app.get('/swagger.json', function(req, res) { - res.sendFile(__dirname + '/swagger.json'); + app.get("/", (req, res) => { + res.render("index.html", { + locals: app.locals }); + }); + + var appPages = { + "/clock-color.html": "clock-color.html" + , "/admin": "adminindex.html" + , "/profile": "profileindex.html" + , "/food": "foodindex.html" + , "/bgclock.html": "bgclock.html" + , "/report": "reportindex.html" + , "/translations": "translationsindex.html" + , "/clock.html": "clock.html" + }; + + Object.keys(appPages).forEach(function(page) { + app.get(page, (req, res) => { + res.render(appPages[page], { + locals: app.locals + }); + }); + }); -/* - if (env.settings.isEnabled('dumps')) { - var heapdump = require('heapdump'); - app.get('/api/v2/dumps/start', function(req, res) { - var path = new Date().toISOString() + '.heapsnapshot'; - path = path.replace(/:/g, '-'); - console.info('writing dump to', path); - heapdump.writeSnapshot(path); - res.send('wrote dump to ' + path); - }); - } -*/ - - //app.get('/package.json', software); + app.get("/appcache/*", (req, res) => { + res.render("nightscout.appcache", { + locals: app.locals + }); + }); + + app.use('/api', bodyParser({ + limit: 1048576 * 50 + }), apiRoot); + + app.use('/api/v1', bodyParser({ + limit: 1048576 * 50 + }), api); + + app.use('/api/v2', bodyParser({ + limit: 1048576 * 50 + }), api); + + app.use('/api/v2/properties', ctx.properties); + app.use('/api/v2/authorization', ctx.authorization.endpoints); + app.use('/api/v2/ddata', ddata); + app.use('/api/v2/notifications', notificationsV2); + + app.use('/api/v3', api3); + + // pebble data + app.get('/pebble', ctx.pebble); + + // expose swagger.json + app.get('/swagger.json', function(req, res) { + res.sendFile(__dirname + '/swagger.json'); + }); + + // expose swagger.yaml + app.get('/swagger.yaml', function(req, res) { + res.sendFile(__dirname + '/swagger.yaml'); + }); + + if (env.settings.isEnabled('dumps')) { + var heapdump = require('heapdump'); + app.get('/api/v2/dumps/start', function(req, res) { + var path = new Date().toISOString() + '.heapsnapshot'; + path = path.replace(/:/g, '-'); + console.info('writing dump to', path); + heapdump.writeSnapshot(path); + res.send('wrote dump to ' + path); + }); + } - // Allow static resources to be cached for week - var maxAge = 7 * 24 * 60 * 60 * 1000; + // app.get('/package.json', software); - if (process.env.NODE_ENV === 'development') { - maxAge = 10; - console.log('Development environment detected, setting static file cache age to 10 seconds'); + // Allow static resources to be cached for week + var maxAge = 7 * 24 * 60 * 60 * 1000; - app.get('/nightscout.appcache', function(req, res) { - res.sendStatus(404); - }); - } + if (process.env.NODE_ENV === 'development') { + maxAge = 1; + console.log('Development environment detected, setting static file cache age to 1 second'); - //TODO: JC - changed cache to 1 hour from 30d ays to bypass cache hell until we have a real solution - var staticFiles = express.static(env.static_files, { - maxAge: maxAge + app.get('/nightscout.appcache', function(req, res) { + res.sendStatus(404); }); + } - // serve the static content - app.use(staticFiles); + var staticFiles = express.static(env.static_files, { + maxAge + }); - var swaggerFiles = express.static(env.swagger_files, { - maxAge: maxAge - }); + // serve the static content + app.use(staticFiles); - // serve the static content - app.use('/swagger-ui-dist', swaggerFiles); + // API docs - var tmpFiles = express.static('tmp', { - maxAge: maxAge - }); + const swaggerUi = require('swagger-ui-express'); + const swaggerDocument = require('./swagger.json'); - // serve the static content - app.use(tmpFiles); - - if (process.env.NODE_ENV !== 'development') { - - console.log('Production environment detected, enabling Minify'); - - var minify = require('express-minify'); - var myUglifyJS = require('uglify-js'); - var myCssmin = require('cssmin'); - - app.use(minify({ - js_match: /\.js/, - css_match: /\.css/, - sass_match: /scss/, - less_match: /less/, - stylus_match: /stylus/, - coffee_match: /coffeescript/, - json_match: /json/, - uglifyJS: myUglifyJS, - cssmin: myCssmin, - cache: __dirname + '/tmp', - onerror: undefined, - })); + app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); - } + app.use('/swagger-ui-dist', (req, res) => { + res.redirect(307, '/api-docs'); + }); - // if this is dev environment, package scripts on the fly - // if production, rely on postinstall script to run packaging for us + // if this is dev environment, package scripts on the fly + // if production, rely on postinstall script to run packaging for us - if (process.env.NODE_ENV === 'development') { + app.locals.bundle = '/bundle'; - var webpack = require("webpack"); - var webpack_conf = require('./webpack.config'); + app.locals.mode = 'production'; - webpack(webpack_conf, function(err, stats) { + if (process.env.NODE_ENV === 'development') { - var json = stats.toJson() // => webpack --json + console.log('Development mode'); - var options = { - noColor: true - }; + app.locals.mode = 'development'; + app.locals.bundle = '/devbundle'; - console.log(prettyjson.render(json.errors, options)); - console.log(prettyjson.render(json.assets, options)); + const webpack = require('webpack'); + var webpack_conf = require('./webpack.config'); + const middleware = require('webpack-dev-middleware'); + const compiler = webpack(webpack_conf); - }); - } + app.use( + middleware(compiler, { + // webpack-dev-middleware options + publicPath: webpack_conf.output.publicPath + , lazy: false + }) + ); - // Handle errors with express's errorhandler, to display more readable error messages. - var errorhandler = require('errorhandler'); - //if (process.env.NODE_ENV === 'development') { - app.use(errorhandler()); - //} - return app; + app.use(require("webpack-hot-middleware")(compiler, { + heartbeat: 1000 + })); + } + + // Production bundling + var tmpFiles; + if (fs.existsSync(process.cwd() + '/tmp/cacheBusterToken')) { + tmpFiles = express.static('tmp', { + maxAge: maxAge + }); + } else { + tmpFiles = express.static(__dirname + '/tmp', { + maxAge: maxAge + }); + } + + // serve the static content + app.use('/bundle', tmpFiles); + + if (process.env.NODE_ENV !== 'development') { + + console.log('Production environment detected, enabling Minify'); + + var minify = require('express-minify'); + var myCssmin = require('cssmin'); + + app.use(minify({ + js_match: /\.js/ + , css_match: /\.css/ + , sass_match: /scss/ + , less_match: /less/ + , stylus_match: /stylus/ + , coffee_match: /coffeescript/ + , json_match: /json/ + , cssmin: myCssmin + , cache: __dirname + '/tmp' + , onerror: undefined + , })); + + } + + // Handle errors with express's errorhandler, to display more readable error messages. + var errorhandler = require('errorhandler'); + //if (process.env.NODE_ENV === 'development') { + app.use(errorhandler()); + //} + return app; } -module.exports = create; \ No newline at end of file +module.exports = create; diff --git a/app.json b/app.json index 17909419c54..3d86d59bfa2 100644 --- a/app.json +++ b/app.json @@ -2,155 +2,150 @@ "name": "CGM Remote Monitor", "repository": "https://github.com/nightscout/cgm-remote-monitor", "env": { - "MONGO_COLLECTION": { - "description": "REQUIRED: The mongo collection used for CGM data. Default value is 'entries'. Most users should use the default.", - "value": "entries", - "required": true + "ALARM_HIGH": { + "description": "Default setting for new browser views, for the High alarm (triggered when BG crosses BG_TARGET_TOP). ('on' or 'off')", + "value": "on", + "required": false }, - "API_SECRET": { - "description": "REQUIRED: A secret passphrase that must be at least 12 characters long, required to enable POST and PUT; also required for the Care Portal.", - "value": "", - "required": true + "ALARM_LOW": { + "description": "Default setting for new browser views, for the Low alarm (triggered when BG crosses BG_TARGET_BOTTOM). ('on' or 'off')", + "value": "on", + "required": false }, - "DISPLAY_UNITS": { - "description": "Choices: mg/dl and mmol. Setting to mmol puts the entire server into mmol mode by default, no further settings needed.", - "value": "", + "ALARM_TIMEAGO_URGENT": { + "description": "Default setting for new browser views, for an urgent alarm when CGM data hasn't been received in the number of minutes set in ALARM_TIMEAGO_URGENT_MINS. ('on' or 'off')", + "value": "on", "required": false }, - "ENABLE": { - "description": "Used to enable optional features, expects a space delimited list, such as: careportal rawbg iob, see https://github.com/nightscout/cgm-remote-monitor/blob/master/README.md for more info.", - "value": "", + "ALARM_TIMEAGO_URGENT_MINS": { + "description": "Default setting for new browser views, for the number of minutes since the last CGM reading to trigger an ALARM_TIMEAGO_URGENT alarm.", + "value": "30", "required": false }, - "DISABLE": { - "description": "Used to disable default features, expects a space delimited list, such as: direction upbat, see https://github.com/nightscout/cgm-remote-monitor/blob/master/README.md for more info.", - "value": "", + "ALARM_TIMEAGO_WARN": { + "description": "Default setting for new browser views, for a warning alarm when CGM data hasn't been received in the number of minutes set in ALARM_TIMEAGO_WARN_MINS. ('on' or 'off')", + "value": "on", + "required": false + }, + "ALARM_TIMEAGO_WARN_MINS": { + "description": "Default setting for new browser views, for the number of minutes since the last CGM reading to trigger an ALARM_TIMEAGO_WARN alarm.", + "value": "15", "required": false }, "ALARM_TYPES": { - "description": "Alarm behavior currently 2 alarm types are supported simple and predict, and can be used independently or combined. The simple alarm type only compares the current BG to BG_ thresholds above, the predict alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. predict DOES NOT currently use any of the BG_* ENV's", - "value": "", + "description": "'simple' and/or 'predict'. Simple alarms trigger when BG crosses the various thresholds set below. Predict alarms use a formula that forecasts where the BG is going based on its trend. You will *not* get warnings when crossing the BG thresholds set below when using the predict type.", + "value": "simple", "required": false }, - "BG_HIGH": { - "description": "Urgent high BG alarm. Default null value implies 260. Must be set in mg/dL (multiply with 18 if you have a value in mmol/L). Only used with simple alarms.", - "value": "", + "ALARM_URGENT_HIGH": { + "description": "Default setting for new browser views, for the Urgent High alarm (triggered when BG crosses BG_HIGH). ('on' or 'off')", + "value": "on", + "required": false + }, + "ALARM_URGENT_LOW": { + "description": "Default setting for new browser views, for the Urgent Low alarm (triggered when BG crosses BG_LOW). ('on' or 'off')", + "value": "on", "required": false }, - "BG_TARGET_TOP": { - "description": "Non-urgent high BG alarm, the top of your target range. Default null value implies 180. Must be set in mg/dL (multiply with 18 if you have a value in mmol/L). Only used with simple alarms.", + "API_SECRET": { + "description": "A passphrase that must be at least 12 characters long. Avoid 'special' characters, which can cause problems in some cases.", "value": "", + "required": true + }, + "BG_HIGH": { + "description": "Urgent High BG threshold, triggers the ALARM_URGENT_HIGH alarm. Set in mg/dL or mmol/L, as set in DISPLAY_UNITS variable.", + "value": "260", + "required": false + }, + "BG_LOW": { + "description": "Urgent Low BG threshold, triggers the ALARM_URGENT_LOW alarm. Set in mg/dL or mmol/L, as set in DISPLAY_UNITS variable.", + "value": "55", "required": false }, "BG_TARGET_BOTTOM": { - "description": "Non urgent low BG alarm, the bottom of your target range. Default null value implies 80. Must be set in mg/dL (multiply with 18 if you have a value in mmol/L). Only used with simple alarms.", - "value": "", + "description": "Low BG threshold, triggers the ALARM_LOW alarm. Set in mg/dL or mmol/L, as set in DISPLAY_UNITS variable.", + "value": "80", "required": false }, - "BG_LOW": { - "description": "Urgent Low BG alarm. Default null value implies 55. Must be set in mg/dL (multiply with 18 if you have a value in mmol/L). Only used with simple alarms.", - "value": "", + "BG_TARGET_TOP": { + "description": "High BG threshold, triggers the ALARM_HIGH alarm. Set in mg/dL or mmol/L, as set in DISPLAY_UNITS variable.", + "value": "180", "required": false }, - "PUSHOVER_API_TOKEN": { - "description": "Pushover API token, required for Pushover notifications. Leave blank if not using Pushover.", + "BRIDGE_PASSWORD": { + "description": "Your Dexcom account password, to receive CGM data from the Dexcom Share service. Also make sure to include 'bridge' in your ENABLE line.", "value": "", "required": false }, - "PUSHOVER_USER_KEY": { - "description": "Pushover user key, required for Pushover notifications. Leave blank if not using Pushover.", - "value": "", + "BRIDGE_SERVER": { + "description": "If you are bridging from the Dexcom Share service, and are anywhere *outside* the US, change this to EU. ('US' or 'EU')", + "value": "US", "required": false }, - "PUSHOVER_ANNOUNCEMENT_KEY": { - "description": "An optional Pushover user/group key, will be used for system wide user generated announcements. If not defined this will fallback to `PUSHOVER_USER_KEY`. A possible use for this is sending important messages and alarms to a CWD that you don't want to send all notification too. This also support a space delimited list of keys. Leave blank if not using Pushover", + "BRIDGE_USER_NAME": { + "description": "Your Dexcom account username, to receive CGM data from the Dexcom Share service. Also make sure to include 'bridge' in your ENABLE line.", "value": "", "required": false }, "CUSTOM_TITLE": { - "description": "Customize the name of the website, usually the name of T1.", + "description": "The display name for the Nightscout site. Appears in the upper left of the main view. Often set to the name of the CGM wearer.", "value": "", "required": false }, - "BRIDGE_USER_NAME": { - "description": "Share bridge - Your user name for the Share service. ENSURE bridge is in ENABLE if you want to use the share bridge.", + "DISPLAY_UNITS": { + "description": "Preferred BG units for the site: 'mg/dl' or 'mmol/L' (or just 'mmol').", + "value": "mg/dl", + "required": true + }, + "ENABLE": { + "description": "Plugins to enable for your site. Must be a space-delimited, lower-case list. Include the word 'bridge' here if you are receiving data from the Dexcom Share service. Include 'mmconnect' if you are bridging from the MiniMed CareLink service.", + "value": "careportal basal", + "required": false + }, + "MMCONNECT_USER_NAME": { + "description": "Your CareLink account username, to receive CGM data from the CareLink service. Also make sure to include 'mmconnect' in your ENABLE line.", "value": "", "required": false }, - "BRIDGE_PASSWORD": { - "description": "Share bridge - Your password for the Share service. ENSURE bridge is in ENABLE if you want to use the share bridge.", + "MMCONNECT_PASSWORD": { + "description": "Your CareLink account password, to receive CGM data from the CareLink service. Also make sure to include 'mmconnect' in your ENABLE line.", "value": "", "required": false }, - "TIME_FORMAT": { - "description": "Browser default time mode. Valid settings are 12 or 24", - "value": "12", - "required": false + "MMCONNECT_SERVER": { + "description": "If you are bridging from the CareLink service, and are anywhere *outside* the US, change this to EU. ('US' or 'EU')", + "value": "US", + "required": false + }, + "MONGO_COLLECTION": { + "description": "The Mongo collection where CGM data is stored.", + "value": "entries", + "required": true }, "NIGHT_MODE": { - "description": "Browser defaults to night mode. Valid settings are on or off", + "description": "Default setting for new browser views, for whether Night Mode should be enabled. ('on' or 'off')", "value": "off", "required": false }, + "SHOW_PLUGINS": { + "description": "Default setting for whether or not these plugins are checked (active) by default, not merely enabled. Include plugins here as in the ENABLE line; space-separated and lower-case.", + "value": "careportal", + "required": false + }, "SHOW_RAWBG": { - "description": "Browser default raw display mode. Valid settings are always, never, or noise", + "description": "Default setting for new browser views, for the display of raw CGM data (if available). ('always', 'never', or 'noise')", "value": "never", "required": false }, "THEME": { - "description": "Browser default theme setting. Valid settings are default, colors, or colorblindfriendly", - "value": "default", + "description": "Default setting for new browser views, for the color theme of the CGM graph. ('default', 'colors', or 'colorblindfriendly')", + "value": "colors", "required": false }, - "ALARM_URGENT_HIGH": { - "description": "Browser default urgent high alarm enabled. Valid settings are on or off", - "value": "on", + "TIME_FORMAT": { + "description": "Default setting for new browser views, for the time mode. ('12' or '24')", + "value": "12", "required": false - }, - "ALARM_HIGH": { - "description": "Browser default high alarm enabled. Valid settings are on or off", - "value": "on", - "required": false - }, - "ALARM_LOW": { - "description": "Browser default low alarm enabled. Valid settings are on or off", - "value": "on", - "required": false - }, - "ALARM_URGENT_LOW": { - "description": "Browser default urgent low alarm enabled. Valid settings are on or off", - "value": "on", - "required": false - }, - "ALARM_TIMEAGO_WARN": { - "description": "Browser default warn after time of last data exceeds ALARM_TIMEAGO_WARN_MINS alarm enabled. Valid settings are on or off", - "value": "on", - "required": false - }, - "ALARM_TIMEAGO_WARN_MINS": { - "description": "Browser default minutes since the last reading to trigger a warning.", - "value": "15", - "required": false - }, - "ALARM_TIMEAGO_URGENT": { - "description": "Browser default urgent warning after time of last data exceeds ALARM_TIMEAGO_URGENT_MINS alarm enabled. Valid settings are on or off", - "value": "on", - "required": false - }, - "ALARM_TIMEAGO_URGENT_MINS": { - "description": "Browser default minutes since last reading to trigger an urgent alarm.", - "value": "30", - "required": false - }, - "MAKER_KEY": { - "description": "Maker Key - Set this to your secret key Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker, Leave blank if not using maker", - "value": "", - "required": false - }, - "MAKER_ANNOUNCEMENT_KEY": { - "description": "Maker Announcement Key - Set this to your secret key for announcements Note for additional info see https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#ifttt-maker , maker should be added to enable if you want to use maker Leave blank if not using maker", - "value": "", - "required": false } }, "addons": [ diff --git a/bundle/bundle.clocks.source.js b/bundle/bundle.clocks.source.js new file mode 100644 index 00000000000..a200f467330 --- /dev/null +++ b/bundle/bundle.clocks.source.js @@ -0,0 +1,9 @@ + +$ = require("jquery"); + +window.Nightscout = { + client: require('../lib/client/clock-client'), + units: require('../lib/units')(), +}; + +console.info('Nightscout clock bundle ready'); \ No newline at end of file diff --git a/bundle/bundle.reports.source.js b/bundle/bundle.reports.source.js new file mode 100644 index 00000000000..c07368543b4 --- /dev/null +++ b/bundle/bundle.reports.source.js @@ -0,0 +1,10 @@ +import './bundle.source'; + +window.Nightscout.report_plugins = require('../lib/report_plugins/')(); + +console.info('Nightscout report bundle ready'); + +// Needed for Hot Module Replacement +if(typeof(module.hot) !== 'undefined') { + module.hot.accept() // eslint-disable-line no-undef + } diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index ea7cc924414..d554744e6e4 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -2,7 +2,6 @@ import '../static/css/drawer.css'; import '../static/css/dropdown.css'; import '../static/css/sgv.css'; - $ = require("jquery"); require('jquery-ui-bundle'); @@ -26,8 +25,12 @@ window.Nightscout = window.Nightscout || {}; window.Nightscout = { client: require('../lib/client'), units: require('../lib/units')(), - report_plugins: require('../lib/report_plugins/')(), admin_plugins: require('../lib/admin_plugins/')() }; -console.info('Nightscout bundle ready'); \ No newline at end of file +console.info('Nightscout bundle ready'); + +// Needed for Hot Module Replacement +if(typeof(module.hot) !== 'undefined') { + module.hot.accept() // eslint-disable-line no-undef + } diff --git a/bundle/index.js b/bundle/index.js deleted file mode 100644 index 2b37869c95b..00000000000 --- a/bundle/index.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -var browserify_express = require('browserify-express'); - -function bundle(env) { - return browserify_express({ - entry: __dirname + '/bundle.source.js', - watch: __dirname + '/../lib/', - mount: '/public/js/bundle.js', - verbose: true, - minify: env.debug.minify, - bundle_opts: { debug: true }, // enable inline sourcemap on js files - write_file: __dirname + '/bundle.out.js' - }); -} - -module.exports = bundle; diff --git a/ci.test.env b/ci.test.env new file mode 100644 index 00000000000..c57e5eeb0c4 --- /dev/null +++ b/ci.test.env @@ -0,0 +1,7 @@ +CUSTOMCONNSTR_mongo=mongodb://127.0.0.1:27017/testdb +API_SECRET=abcdefghij123 +HOSTNAME=localhost +INSECURE_USE_HTTP=true +PORT=1337 +NODE_ENV=production +CI=true \ No newline at end of file diff --git a/docs/plugins/add-virtual-assistant-support-to-plugin.md b/docs/plugins/add-virtual-assistant-support-to-plugin.md new file mode 100644 index 00000000000..60ac1d1957b --- /dev/null +++ b/docs/plugins/add-virtual-assistant-support-to-plugin.md @@ -0,0 +1,62 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Adding Virtual Assistant Support to a Plugin](#adding-virtual-assistant-support-to-a-plugin) + - [Intent Handlers](#intent-handlers) + - [Rollup handlers](#rollup-handlers) + + + +Adding Virtual Assistant Support to a Plugin +========================================= + +To add virtual assistant support to a plugin, the `init` method of the plugin should return an object that contains a `virtAsst` key. Here is an example: + +```javascript +iob.virtAsst = { + intentHandlers: [{ + intent: "MetricNow" + , metrics: ["iob"] + , intentHandler: virtAsstIOBIntentHandler + }] + , rollupHandlers: [{ + rollupGroup: "Status" + , rollupName: "current iob" + , rollupHandler: virtAsstIOBRollupHandler + }] +}; +``` + +There are 2 types of handlers that you can supply: +* Intent handler - Enables you to "teach" the virtual assistant how to respond to a user's question. +* A rollup handler - Enables you to create a command that aggregates information from multiple plugins. This would be akin to the a "flash briefing". An example would be a status report that contains your current bg, iob, and your current basal. + +### Intent Handlers + +A plugin can expose multiple intent handlers (e.g. useful when it can supply multiple kinds of metrics). Each intent handler should be structured as follows: ++ `intent` - This is the intent this handler is built for. Right now, the templates used by both Alexa and Google Home use only the `"MetricNow"` intent (used for getting the present value of the requested metric) ++ `metrics` - An array of metric name(s) the handler will supply. e.g. "What is my `metric`" - iob, bg, cob, etc. Make sure to add the metric name and its synonyms to the list of metrics used by the virtual assistant(s). + - **IMPORTANT NOTE:** There is no protection against overlapping metric names, so PLEASE make sure your metric name is unique! + - Note: Although this value *is* an array, you really should only supply one (unique) value, and then add aliases or synonyms to that value in the list of metrics for the virtual assistant. We keep this value as an array for backwards compatibility. ++ `intenthandler` - This is a callback function that receives 3 arguments: + - `callback` Call this at the end of your function. It requires 2 arguments: + - `title` - Title of the handler. This is the value that will be displayed on the Alexa card (for devices with a screen). The Google Home response doesn't currently display a card, so it doesn't use this value. + - `text` - This is text that the virtual assistant should speak (and show, for devices with a screen). + - `slots` - These are the slots (Alexa) or parameters (Google Home) that the virtual assistant detected (e.g. `pwd` as seen in the templates is a slot/parameter. `metric` is technically a slot, too). + - `sandbox` - This is the Nightscout sandbox that allows access to various functions. + +### Rollup handlers + +A plugin can also expose multiple rollup handlers ++ `rollupGroup` - This is the key that is used to aggregate the responses when the intent is invoked ++ `rollupName` - This is the name of the handler. Primarily used for debugging ++ `rollupHandler` - This is a callback function that receives 3 arguments + - `slots` - These are the values of the slots. Make sure to add these values to the appropriate custom slot + - `sandbox` - This is the nightscout sandbox that allows access to various functions. + - `callback` - + - `error` - This would be an error message + - `response` - A simple object that expects a `results` string and a `priority` integer. Results should be the text (speech) that is added to the rollup and priority affects where in the rollup the text should be added. The lowest priority is spoken first. An example callback: + ```javascript + callback(null, {results: "Hello world", priority: 1}); + ``` diff --git a/docs/plugins/alexa-plugin.md b/docs/plugins/alexa-plugin.md new file mode 100644 index 00000000000..4e298df4b74 --- /dev/null +++ b/docs/plugins/alexa-plugin.md @@ -0,0 +1,155 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Nightscout Alexa Plugin](#nightscout-alexa-plugin) + - [Overview](#overview) + - [Activate the Nightscout Alexa Plugin](#activate-the-nightscout-alexa-plugin) + - [Create Your Alexa Skill](#create-your-alexa-skill) + - [Get an Amazon Developer account](#get-an-amazon-developer-account) + - [Create a new Alexa skill](#create-a-new-alexa-skill) + - [Define the interaction model](#define-the-interaction-model) + - [Point your skill at your site](#point-your-skill-at-your-site) + - [Test your skill out with the test tool](#test-your-skill-out-with-the-test-tool) + - [What questions can you ask it?](#what-questions-can-you-ask-it) + - [Activate the skill on your Echo or other device](#activate-the-skill-on-your-echo-or-other-device) + - [Updating your skill with new features](#updating-your-skill-with-new-features) + - [Adding support for additional languages](#adding-support-for-additional-languages) + - [Adding Alexa support to a plugin](#adding-alexa-support-to-a-plugin) + + + +Nightscout Alexa Plugin +====================================== + +## Overview + +To add Alexa support for your Nightscout site, here's what you need to do: + +1. [Activate the `alexa` plugin](#activate-the-nightscout-alexa-plugin) on your Nightscout site, so your site will respond correctly to Alexa's requests. +1. [Create a custom Alexa skill](#create-your-alexa-skill) that points at your site and defines certain questions you want to be able to ask. (You'll copy and paste a basic template for this, to keep things simple.) + +To add Alexa support for a plugin, [check this out](#adding-alexa-support-to-a-plugin). + +## Activate the Nightscout Alexa Plugin + +1. Your Nightscout site needs to be new enough that it supports the `alexa` plugin. It needs to be [version 0.9.1 (Grilled Cheese)](https://github.com/nightscout/cgm-remote-monitor/releases/tag/0.9.1) or later. See [updating my version](https://github.com/nightscout/cgm-remote-monitor#updating-my-version) if you need a newer version. +1. Add `alexa` to the list of plugins in your `ENABLE` setting. ([Environment variables](https://github.com/nightscout/cgm-remote-monitor#environment) are set in the configuration section for your monitor. Typically Azure, Heroku, etc.) +1. The Alexa plugin pulls its units preferences from your site's defaults. If you don't have a `DISPLAY_UNITS` entry, it will default to `mg/dl`. If you want it to use mmol/L, make sure you have a `DISPLAY_UNITS` line, and set it to `mmol` (*not* `mmol/l`). + +## Create Your Alexa Skill + +### Get an Amazon Developer account + +1. Sign up for a free [Amazon Developer account](https://developer.amazon.com/) if you don't already have one. +1. [Register](https://developer.amazon.com/docs/devconsole/test-your-skill.html#h2_register) your Alexa-enabled device with your Developer account. +1. Sign in and go to the [Alexa developer portal](https://developer.amazon.com/alexa/console/ask). + +### Create a new Alexa skill + +1. Select "Alexa Skills Kit" in the main menu bar. +1. Click the "Start a Skill" button. This will take you to the Skills console. +1. Click the "Create Skill" button on the Skills console page. +1. Name your new skill "Nightscout" (or something else, if you like). Use English (US) as your language. Click "Next". +1. Choose a model to add to your skill. Click "Select" under "Custom" model, then click "Create skill" on the upper right. +1. Congrats! Your empty custom skill should now be created. + +### Define the interaction model + +Your Alexa skill's "interaction model" defines how your spoken questions get translated into requests to your Nightscout site, and how your Nightscout site's responses get translated into the audio responses that Alexa says back to you. + +To get up and running with an interaction model, which will allow you to ask Alexa a few basic questions about your Nightscout site, you can copy and paste the configuration code for your language from [the list of templates](alexa-templates/). + +- If you're language doesn't have a template, please consider starting with [the en-us template](alexa-templates/en-us.json), then [modifying it to work with your language](#adding-support-for-additional-languages), and [making a pull request](/CONTRIBUTING.md) or [submitting an issue](https://github.com/nightscout/cgm-remote-monitor/issues) with your translated template to share it with others. + +Select "JSON Editor" in the left-hand menu on your skill's edit page (which you should be on if you followed the above instructions). Replace everything in the textbox with the code from your chosen template. Then click "Save Model" at the top. A success message should appear indicating that the model was saved. + +Next you need to build your custom model. Click "Build Model" at the top of the same page. It'll take a minute to build, and then you should see another success message, "Build Successful". + +You now have a custom Alexa skill that knows how to talk to a Nightscout site. + +### Point your skill at your site + +Now you need to point your skill at *your* Nightscout site. + +1. In the left-hand menu for your skill, there's an option called "Endpoint". Click it. +1. Under "Service Endpoint Type", select "HTTPS". +1. You only need to set up the Default Region. In the box that says "Enter URI...", put in `https://{yourdomain}/api/v1/alexa`. (So if your Nightscout site is at `mynightscoutsite.herokuapp.com`, you'll enter `https://mynightscoutsite.herokuapp.com/api/v1/alexa` in the box.) +1. In the dropdown under the previous box, select "My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority". +1. Click the "Save Endpoints" button at the top. +1. You should see a success message pop up when the save succeeds. + +### Test your skill out with the test tool + +Click on the "Test" tab on the top menu. This will take you to the page where you can test your new skill. + +Enable testing for your skill (click the toggle). As indicated on this page, when testing is enabled, you can interact with the development version of your skill in the Alexa simulator and on all devices linked to your Alexa developer account. (Your skill will always be a development version. There's no need to publish it to the public.) + +After you enable testing, you can also use the Alexa Simulator in the left column, to try out the skill. You can type in questions and see the text your skill would reply with. When typing your test question, only type what you would verbally say to an Alexa device after the wake word. (e.g. you would verbally say "Alexa, ask Nightscout how am I doing", so you would type only "ask Nightscout how am I doing") You can also hold the microphone icon to ask questions using your microphone, and you'll get the audio and text responses back. + +##### What questions can you ask it? + +See [Interacting with Virtual Assistants](interacting-with-virtual-assistants.md) for details on what you can do with Alexa. + +### Activate the skill on your Echo or other device + +If your device is [registered](https://developer.amazon.com/docs/devconsole/test-your-skill.html#h2_register) with your developer account, you should be able to use your skill right away. Try it by asking Alexa one of the above questions using your device. + +## Updating your skill with new features + +As more work is done on Nightscout, new ways to interact with Nighscout via Alexa may be made available. To be able to use these new features, you first will need to [update your Nightscout site](https://github.com/nightscout/cgm-remote-monitor#updating-my-version), and then you can follow the steps below to update your Alexa skill. + +1. Make sure you've [updated your Nightscout site](https://github.com/nightscout/cgm-remote-monitor#updating-my-version) first. +1. Open [the latest skill template](alexa-templates/) in your language. You'll be copying the contents of the file later. + - If your language doesn't include the latest features you're looking for, you're help [translating those new features](#adding-support-for-additional-languages) would be greatly appreciated! +1. Sign in to the [Alexa developer portal](https://developer.amazon.com/alexa/console/ask). +1. Open your Nightscout skill. +1. Open the "JSON Editor" in the left navigation pane. +1. Select everything in the text box (Ctrl + A on Windows, Cmd + A on Mac) and delete it. +1. Copy the contents of the updated template and paste it in the text box in the JSON Editor page. +1. Click the "Save Model" button near the top of the page, and then click the "Build Model" button. +1. Make sure to follow any directions specific to the Nightscout update. If there are any, they will be noted in the [release notes](https://github.com/nightscout/cgm-remote-monitor/releases). +1. If you gave your skill name something other than "night scout," you will need to go to the "Invocation" page in the left navigation pane and change the Skill Invocation Name back to your preferred name. Make sure to click the "Save Model" button followed by the "Build Model" button after you change the name. +1. Enjoy the new features! + +## Adding support for additional languages + +If the translations in Nightscout are configured correctly for the desired language code, Nightscout *should* automatically respond in that language after following the steps below. + +If you add support for another language, please consider [making a pull request](/CONTRIBUTING.md) or [submitting an issue](https://github.com/nightscout/cgm-remote-monitor/issues) with your translated template to share it with others. You can export your translated template by going to the "JSON Editor" in the left navigation pane. + +1. Open the Build tab of your Alexa Skill. + - Get to your list of Alexa Skills at https://developer.amazon.com/alexa/console/ask and click on the name of the skill. +1. Click on the language drop-down box in the upper right corner of the window. +1. Click "Language settings". +1. Add your desired language. +1. Click the "Save" button. +1. Navigate to "CUSTOM" in the left navigation pane. +1. Select your new language in the language drop-down box. +1. Go to "JSON Editor" (just above "Interfaces" in the left navigation pane). +1. Remove the existing contents in the text box, and copy and paste the configuration code from a familiar language in [the list of templates](alexa-templates/). +1. Click "Save Model". +1. Click the "Add" button next to the "Slot Types" section in the left pane. +1. Click the radio button for "Use an existing slot type from Alexa's built-in library" +1. In the search box just below that option, search for "first name" +1. If your language has an option, click the "Add Slot Type" button for that option. + - If your language doesn't have an option, you won't be able to ask Nightscout a question that includes a name. +1. For each Intent listed in the left navigation pane (e.g. "NSStatus" and "MetricNow"): + 1. Click on the Intent name. + 1. Scroll down to the "Slots" section + 1. If there's a slot with the name "pwd", change the Slot Type to the one found above. + - If you didn't find one above, you'll have to see if another language gets close enough for you, or delete the slot. + 1. If there's a slot with the name "metric", click the "Edit Dialog" link on the right. This is where you set Alexa's questions and your answers if you happen to ask a question about metrics but don't include which metric you want to know. + 1. Set the "Alexa speech prompts" in your language, and remove the old ones. + 1. Under "User utterances", set the phrases you would say in response to the questions Alexa would pose from the previous step. MAKE SURE that your example phrases include where you would say the name of the metric. You do this by typing the left brace (`{`) and then selecting `metric` in the popup. + 1. Click on the Intent name (just to the left of "metric") to return to the previous screen. + 1. For each Sample Utterance, add an equivalent phrase in your language. If the phrase you're replacing has a `metric` slot, make sure to include that in your replacement phrase. Same goes for the `pwd` slot, unless you had to delete that slot a couple steps ago, in which case you need to modify the phrase to not use a first name, or not make a replacement phrase. After you've entered your replacement phrase, delete the phrase you're replacing. +1. Navigate to the "LIST_OF_METRICS" under the Slot Types section. +1. For each metric listed, add synonyms in your language, and delete the old synonyms. + - What ever you do, **DO NOT** change the text in the "VALUE" column! Nightscout will be looking for these exact values. Only change the synonyms. +1. Click "Save Model" at the top, and then click on "Build Model". +1. You should be good to go! Feel free to try it out using the "Test" tab near the top of the window, or start asking your Alexa-enabled device some questions. See [Interacting with Virtual Assistants](interacting-with-virtual-assistants.md) for details on what you can do with Alexa. + +## Adding Alexa support to a plugin + +See [Adding Virtual Assistant Support to a Plugin](add-virtual-assistant-support-to-plugin.md) \ No newline at end of file diff --git a/docs/plugins/alexa-templates/en-us.json b/docs/plugins/alexa-templates/en-us.json new file mode 100644 index 00000000000..4cb10aa0643 --- /dev/null +++ b/docs/plugins/alexa-templates/en-us.json @@ -0,0 +1,222 @@ +{ + "interactionModel": { + "languageModel": { + "invocationName": "night scout", + "intents": [ + { + "name": "NSStatus", + "slots": [], + "samples": [ + "How am I doing" + ] + }, + { + "name": "LastLoop", + "slots": [], + "samples": [ + "When was my last loop" + ] + }, + { + "name": "MetricNow", + "slots": [ + { + "name": "metric", + "type": "LIST_OF_METRICS", + "samples": [ + "what {pwd} {metric} is", + "what my {metric} is", + "how {pwd} {metric} is", + "how my {metric} is", + "how much {metric} does {pwd} have", + "how much {metric} I have", + "how much {metric}", + "{pwd} {metric}", + "{metric}", + "my {metric}" + ] + }, + { + "name": "pwd", + "type": "AMAZON.US_FIRST_NAME" + } + ], + "samples": [ + "how much {metric} does {pwd} have left", + "what's {metric}", + "what's my {metric}", + "how much {metric} is left", + "what's {pwd} {metric}", + "how much {metric}", + "how is {metric}", + "how is my {metric}", + "how is {pwd} {metric}", + "how my {metric} is", + "what is {metric}", + "how much {metric} do I have", + "how much {metric} does {pwd} have", + "how much {metric} I have", + "what is my {metric}", + "what my {metric} is", + "what is {pwd} {metric}" + ] + }, + { + "name": "AMAZON.NavigateHomeIntent", + "samples": [] + }, + { + "name": "AMAZON.StopIntent", + "samples": [] + } + ], + "types": [ + { + "name": "LIST_OF_METRICS", + "values": [ + { + "name": { + "value": "uploader battery", + "synonyms": [ + "uploader battery remaining", + "uploader battery power" + ] + } + }, + { + "name": { + "value": "pump reservoir", + "synonyms": [ + "remaining insulin", + "insulin remaining", + "insulin is left", + "insulin left", + "insulin in my pump", + "insulin" + ] + } + }, + { + "name": { + "value": "pump battery", + "synonyms": [ + "pump battery remaining", + "pump battery power" + ] + } + }, + { + "name": { + "value": "bg", + "synonyms": [ + "number", + "blood sugar", + "blood glucose" + ] + } + }, + { + "name": { + "value": "iob", + "synonyms": [ + "insulin on board" + ] + } + }, + { + "name": { + "value": "basal", + "synonyms": [ + "current basil", + "basil", + "current basal" + ] + } + }, + { + "name": { + "value": "cob", + "synonyms": [ + "carbs", + "carbs on board", + "carboydrates", + "carbohydrates on board" + ] + } + }, + { + "name": { + "value": "forecast", + "synonyms": [ + "ar2 forecast", + "loop forecast" + ] + } + }, + { + "name": { + "value": "raw bg", + "synonyms": [ + "raw number", + "raw blood sugar", + "raw blood glucose" + ] + } + } + ] + } + ] + }, + "dialog": { + "intents": [ + { + "name": "MetricNow", + "confirmationRequired": false, + "prompts": {}, + "slots": [ + { + "name": "metric", + "type": "LIST_OF_METRICS", + "confirmationRequired": false, + "elicitationRequired": true, + "prompts": { + "elicitation": "Elicit.Slot.1421281086569.34001419564" + } + }, + { + "name": "pwd", + "type": "AMAZON.US_FIRST_NAME", + "confirmationRequired": false, + "elicitationRequired": false, + "prompts": {} + } + ] + } + ], + "delegationStrategy": "ALWAYS" + }, + "prompts": [ + { + "id": "Elicit.Slot.1421281086569.34001419564", + "variations": [ + { + "type": "PlainText", + "value": "What metric are you looking for?" + }, + { + "type": "PlainText", + "value": "What value are you looking for?" + }, + { + "type": "PlainText", + "value": "What metric do you want to know?" + }, + { + "type": "PlainText", + "value": "What value do you want to know?" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/docs/plugins/google-home-templates/en-us.zip b/docs/plugins/google-home-templates/en-us.zip new file mode 100644 index 00000000000..6a8498b0b19 Binary files /dev/null and b/docs/plugins/google-home-templates/en-us.zip differ diff --git a/docs/plugins/googlehome-plugin.md b/docs/plugins/googlehome-plugin.md new file mode 100644 index 00000000000..ccffb81f404 --- /dev/null +++ b/docs/plugins/googlehome-plugin.md @@ -0,0 +1,140 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Nightscout Google Home/DialogFlow Plugin](#nightscout-google-homedialogflow-plugin) + - [Overview](#overview) + - [Activate the Nightscout Google Home Plugin](#activate-the-nightscout-google-home-plugin) + - [Create Your DialogFlow Agent](#create-your-dialogflow-agent) + - [What questions can you ask it?](#what-questions-can-you-ask-it) + - [Updating your agent with new features](#updating-your-agent-with-new-features) + - [Adding support for additional languages](#adding-support-for-additional-languages) + - [Adding Google Home support to a plugin](#adding-google-home-support-to-a-plugin) + + + +Nightscout Google Home/DialogFlow Plugin +======================================== + +## Overview + +To add Google Home support for your Nightscout site, here's what you need to do: + +1. [Activate the `googlehome` plugin](#activate-the-nightscout-google-home-plugin) on your Nightscout site, so your site will respond correctly to Google's requests. +1. [Create a custom DialogFlow agent](#create-your-dialogflow-agent) that points at your site and defines certain questions you want to be able to ask. + +## Activate the Nightscout Google Home Plugin + +1. Your Nightscout site needs to be new enough that it supports the `googlehome` plugin. It needs to be [version 13.0.0 (Ketchup)](https://github.com/nightscout/cgm-remote-monitor/releases/tag/13.0.0) or later. See [updating my version](https://github.com/nightscout/cgm-remote-monitor#updating-my-version) if you need a newer version. +1. Add `googlehome` to the list of plugins in your `ENABLE` setting. ([Environment variables](https://github.com/nightscout/cgm-remote-monitor#environment) are set in the configuration section for your monitor. Typically Azure, Heroku, etc.) + +## Create Your DialogFlow Agent + +1. Download the agent template in your language for Google Home [here](google-home-templates/). + - If you're language doesn't have a template, please consider starting with [the en-us template](google-home-templates/en-us.zip), then [modifying it to work with your language](#adding-support-for-additional-languages), and [making a pull request](/CONTRIBUTING.md) or [submitting an issue](https://github.com/nightscout/cgm-remote-monitor/issues) with your translated template to share it with others. +1. [Sign in to Google's Action Console](https://console.actions.google.com) + - Make sure to use the same account that is connected to your Google Home device, Android smartphone, Android tablet, etc. +1. Click on the "New Project" button. +1. If prompted, agree to the Terms of Service. +1. Give your project a name (e.g. "Nightscout") and then click "Create project". +1. For the "development experience", select "Conversational" at the bottom of the list. +1. Click on the "Develop" tab at the top of the sreen. +1. Click on "Invocation" in the left navigation pane. +1. Set the display name (e.g. "Night Scout") of your Action and set your Google Assistant voice. + - Unfortunately, the Action name needs to be two words, and is required to be unique across all of Google, even though you won't be publishing this for everyone on Google to use. So you'll have to be creative with the name since "Night Scout" is already taken. +1. Click "Save" in the upper right corner. +1. Navigate to "Actions" in the left nagivation pane, then click on the "Add your first action" button. +1. Make sure you're on "Cutom intent" and then click "Build" to open DialogFlow in a new tab. +1. Sign in with the same Google account you used to sign in to the Actions Console. + - You'll have to go through the account setup steps if this is your first time using DialogFlow. +1. Verify the name for your agent (e.g. "Nightscout") and click "CREATE". +1. In the navigation pane on the left, click the gear icon next to your agent name. +1. Click on the "Export and Import" tab in the main area of the page. +1. Click the "IMPORT FROM ZIP" button. +1. Select the template file downloaded in step 1. +1. Type "IMPORT" where requested and then click the "IMPORT" button. +1. After the import finishes, click the "DONE" button followed by the "SAVE" button. +1. In the navigation pane on the left, click on "Fulfillment". +1. Enable the toggle for "Webhook" and then fill in the URL field with your Nightscout URL: `https://YOUR-NIGHTSCOUT-SITE/api/v1/googlehome` +1. Scroll down to the bottom of the page and click the "SAVE" button. +1. Click on "Integrations" in the navigation pane. +1. Click on "INTEGRATION SETTINGS" for "Google Assistant". +1. Under "Implicit invocation", add every intent listed. +1. Turn on the toggle for "Auto-preview changes". +1. Click "CLOSE". + +That's it! Now try asking Google "Hey Google, ask *your Action's name* how am I doing?" + +### What questions can you ask it? + +See [Interacting with Virtual Assistants](interacting-with-virtual-assistants.md) for details on what you can do with Google Home. + +## Updating your agent with new features + +As more work is done on Nightscout, new ways to interact with Nighscout via Google Home may be made available. To be able to use these new features, you first will need to [update your Nightscout site](https://github.com/nightscout/cgm-remote-monitor#updating-my-version), and then you can follow the steps below to update your DialogFlow agent. + +1. Make sure you've [updated your Nightscout site](https://github.com/nightscout/cgm-remote-monitor#updating-my-version) first. +1. Download [the latest skill template](google-home-templates/) in your language. + - If your language doesn't include the latest features you're looking for, you're help [translating those new features](#adding-support-for-additional-languages) would be greatly appreciated! +1. Sign in to the [DialogFlow developer portal](https://dialogflow.cloud.google.com/). +1. Make sure you're viewing your Nightscout agent (there's a drop-down box immediately below the DialogFlow logo where you can select your agent). +1. Click on the gear icon next to your agent name, then click on the "Export and Import" tab. +1. Click the "RESTORE FROM ZIP" button. +1. Select the template file you downloaded earlier, then type "RESTORE" in the text box as requested, and click the "RESTORE" button. +1. After the import is completed, click the "DONE" button. +1. Make sure to follow any directions specific to the Nightscout update. If there are any, they will be noted in the [release notes](https://github.com/nightscout/cgm-remote-monitor/releases). +1. Enjoy the new features! + +## Adding support for additional languages + +If the translations in Nightscout are configured correctly for the desired language code, Nightscout *should* automatically respond in that language after following the steps below. + +If you add support for another language, please consider [making a pull request](/CONTRIBUTING.md) or [submitting an issue](https://github.com/nightscout/cgm-remote-monitor/issues) with your translated template to share it with others. You can export your translated template by going to the settings of your DialogFlow agent (the gear icon next to the project's name in the left nagivation pane), going to the "Export and Import" tab, and clicking "EXPORT AS ZIP". + +1. Open your DialogFlow agent. + - Get to your list of agents at https://console.dialogflow.com/api-client/#/agents and click on the name of your Nightscout agent. +1. Click on the "Languages" tab. +1. Click the "Add Additional Language" drop-down box. +1. Select your desired language. +1. Click the "SAVE" button. + - Note the new language code below the agent's name. e.g. if you're using the English template and you added Spanish, you would see two buttons: "en" and "es". +1. Click on "Intents" in the left navigation pane. +1. For each intent in the list (NOT including those that start with "Default" in the name): + 1. Click on the intent name. + 1. Note the phrases used in the "Training phrases" section. + - If the phrase has a colored block (e.g. `metric` or `pwd`), click the phrase (but NOT the colored block) and note the "PARAMETER NAME" of the item with the same-colored "ENTITY". + 1. Click on the new language code (beneath the agent name near the top of the navigation pane). + 1. Add equivalent or similar training phrases as those you noted a couple steps ago. + - If the phrase in the orginal language has a colored block with a word in it, that needs to be included. When adding the phrase to the new language, follow these steps to add the colored block: + 1. When typing that part of the training phrase, don't translate the word in the block; just keep it as-is. + 1. After typing the phrase (DON'T push the Enter key yet!) highlight/select the word. + 1. A box will pop up with a list of parameter types, some of which end with a colon (`:`) and a parameter name. Click the option that has the same parameter name as the one you determined just a few steps ago. + 1. Press the Enter key to add the phrase. + 1. Click the "SAVE" button. + 1. Go back and forth between your starting language and your new language, adding equivalent phrase(s) to the new language. Continue once you've added all the equivalent phrases you can think of. + 1. Scroll down to the "Action and parameters" section. + 1. If any of the items in that list have the "REQUIRED" option checked: + 1. Click the "Define prompts..." link on the right side of that item. + 1. Add phrases that Google will ask if you happen to say something similar to a training phrase, but don't include this parameter (e.g. if you ask about a metric but don't say what metric you want to know about). + 1. Click "CLOSE". + 1. Scroll down to the "Responses" section. + 1. Set just one phrase here. This will be what Google says if it has technical difficulties getting a response from your Nightscout website. + 1. Click the "SAVE" button at the top of the window. +1. Click on the "Entities" section in the navigation pane. +1. For each entity listed: + 1. Click the entity name. + 1. Switch to the starting language (beneath the agent name near the top of the left navigation pane). + 1. Click the menu icon to the right of the "SAVE" button and click "Switch to raw mode". + 1. Select all the text in the text box and copy it. + 1. Switch back to your new language. + 1. Click the menu icon to the right of the "SAVE" button and click "Switch to raw mode". + 1. In the text box, paste the text you just copied. + 1. Click the menu icon to the right of the "SAVE" button and click "Switch to editor mode". + 1. For each item in the list, replace the items on the RIGHT side of the list with equivalent words and phrases in your language. + - What ever you do, **DO NOT** change the values on the left side of the list. Nightscout will be looking for these exact values. Only change the items on the right side of the list. + 1. Click the "SAVE" button. +1. You should be good to go! Feel free to try it out by click the "See how it works in Google Assistant" link in the right navigation pane, or start asking your Google-Home-enabled device some questions. See [Interacting with Virtual Assistants](interacting-with-virtual-assistants.md) for details on what you can do with Google Home. + +## Adding Google Home support to a plugin + +See [Adding Virtual Assistant Support to a Plugin](add-virtual-assistant-support-to-plugin.md) \ No newline at end of file diff --git a/docs/plugins/interacting-with-virtual-assistants.md b/docs/plugins/interacting-with-virtual-assistants.md new file mode 100644 index 00000000000..984a876f21c --- /dev/null +++ b/docs/plugins/interacting-with-virtual-assistants.md @@ -0,0 +1,67 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Interacting with Virtual Assistants](#interacting-with-virtual-assistants) +- [Alexa vs. Google Home](#alexa-vs-google-home) +- [What questions can you ask it?](#what-questions-can-you-ask-it) + - [A note about names](#a-note-about-names) + + + +Interacting with Virtual Assistants +=================================== + +# Alexa vs. Google Home + +Although these example phrases reference Alexa, the exact same questions could be asked of Google. +Just replace "Alexa, ask Nightscout ..." with "Hey Google, ask *your action's name* ..." + +# What questions can you ask it? + +This list is not meant to be comprehensive, nor does it include every way you can ask the questions. To get the full picture, in the respective console for your virtual assistant, check the example phrases for each `intent`, and the values (including synonyms) of the "metric" `slot` (Alexa) or `entity` (Google Home). You can also just experiement with asking different questions to see what works. + +*Forecast:* + +- "Alexa, ask Nightscout how am I doing" +- "Alexa, ask Nightscout how I'm doing" + +*Uploader Battery:* + +- "Alexa, ask Nightscout how is my uploader battery" + +*Pump Battery:* + +- "Alexa, ask Nightscout how is my pump battery" + +*Metrics:* + +- "Alexa, ask Nightscout what my bg is" +- "Alexa, ask Nightscout what my blood glucose is" +- "Alexa, ask Nightscout what my number is" +- "Alexa, ask Nightscout what is my insulin on board" +- "Alexa, ask Nightscout what is my basal" +- "Alexa, ask Nightscout what is my current basal" +- "Alexa, ask Nightscout what is my cob" +- "Alexa, ask Nightscout what is Charlie's carbs on board" +- "Alexa, ask Nightscout what is Sophie's carbohydrates on board" +- "Alexa, ask Nightscout what is Harper's loop forecast" +- "Alexa, ask Nightscout what is Alicia's ar2 forecast" +- "Alexa, ask Nightscout what is Peter's forecast" +- "Alexa, ask Nightscout what is Arden's raw bg" +- "Alexa, ask Nightscout what is Dana's raw blood glucose" + +*Insulin Remaining:* + +- "Alexa, ask Nightscout how much insulin do I have left" +- "Alexa, ask Nightscout how much insulin do I have remaining" +- "Alexa, ask Nightscout how much insulin does Dana have left? +- "Alexa, ask Nightscout how much insulin does Arden have remaining? + +*Last Loop:* + +- "Alexa, ask Nightscout when was my last loop" + +## A note about names + +All the formats with specific names will respond to questions for any first name. You don't need to configure anything with your PWD's name. \ No newline at end of file diff --git a/docs/plugins/maker-setup-images/action_search.jpg b/docs/plugins/maker-setup-images/action_search.jpg new file mode 100644 index 00000000000..b3b85b0b7c1 Binary files /dev/null and b/docs/plugins/maker-setup-images/action_search.jpg differ diff --git a/docs/plugins/maker-setup-images/blue_that.jpg b/docs/plugins/maker-setup-images/blue_that.jpg new file mode 100644 index 00000000000..6a52475bcb1 Binary files /dev/null and b/docs/plugins/maker-setup-images/blue_that.jpg differ diff --git a/docs/plugins/maker-setup-images/config_vars_enable.jpg b/docs/plugins/maker-setup-images/config_vars_enable.jpg new file mode 100644 index 00000000000..508464f156c Binary files /dev/null and b/docs/plugins/maker-setup-images/config_vars_enable.jpg differ diff --git a/docs/plugins/maker-setup-images/config_vars_maker.jpg b/docs/plugins/maker-setup-images/config_vars_maker.jpg new file mode 100644 index 00000000000..fdb8765f836 Binary files /dev/null and b/docs/plugins/maker-setup-images/config_vars_maker.jpg differ diff --git a/docs/plugins/maker-setup-images/maker_key.jpg b/docs/plugins/maker-setup-images/maker_key.jpg new file mode 100644 index 00000000000..8e570032762 Binary files /dev/null and b/docs/plugins/maker-setup-images/maker_key.jpg differ diff --git a/docs/plugins/maker-setup-images/notification_message.jpg b/docs/plugins/maker-setup-images/notification_message.jpg new file mode 100644 index 00000000000..3db0278bc69 Binary files /dev/null and b/docs/plugins/maker-setup-images/notification_message.jpg differ diff --git a/docs/plugins/maker-setup-images/service_search.jpg b/docs/plugins/maker-setup-images/service_search.jpg new file mode 100644 index 00000000000..49910be5e06 Binary files /dev/null and b/docs/plugins/maker-setup-images/service_search.jpg differ diff --git a/docs/plugins/maker-setup-images/service_settings_search.jpg b/docs/plugins/maker-setup-images/service_settings_search.jpg new file mode 100644 index 00000000000..2db00726482 Binary files /dev/null and b/docs/plugins/maker-setup-images/service_settings_search.jpg differ diff --git a/docs/plugins/maker-setup-images/set_trigger.jpg b/docs/plugins/maker-setup-images/set_trigger.jpg new file mode 100644 index 00000000000..27884f8ac04 Binary files /dev/null and b/docs/plugins/maker-setup-images/set_trigger.jpg differ diff --git a/docs/plugins/maker-setup-images/webhooks_settings.jpg b/docs/plugins/maker-setup-images/webhooks_settings.jpg new file mode 100644 index 00000000000..e94f4485c43 Binary files /dev/null and b/docs/plugins/maker-setup-images/webhooks_settings.jpg differ diff --git a/docs/plugins/maker-setup.md b/docs/plugins/maker-setup.md new file mode 100644 index 00000000000..f40c94f161c --- /dev/null +++ b/docs/plugins/maker-setup.md @@ -0,0 +1,128 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Nightscout/IFTTT Maker](#nightscoutifttt-maker) + - [Overview](#overview) + - [Note: There have been some recent reports of the IFTTT service delaying Nightscout alarms. Be sure to test your implementation before relying solely on its alerts. Pushover is an alternate push notification service that might be worth considering as well.](#note-there-have-been-some-recent-reports-of-the-ifttt-service-delaying-nightscout-alarms-be-sure-to-test-your-implementation-before-relying-solely-on-its-alerts-pushover-is-an-alternate-push-notification-service-that-might-be-worth-considering-as-well) + - [Events](#events) + - [Creating an Applet](#creating-an-applet) + - [1. Choose a Service](#1-choose-a-service) + - [2. Choose a Trigger](#2-choose-a-trigger) + - [3. Complete the Trigger field](#3-complete-the-trigger-field) + - [4. Create an Action](#4-create-an-action) + - [5. Complete Action Fields](#5-complete-action-fields) + - [6. Review and Finish](#6-review-and-finish) + - [7. Get your Maker Key](#7-get-your-maker-key) + - [8. Configure your Nightscout site](#8-configure-your-nightscout-site) + - [9. Configure the IFTTT mobile app](#9-configure-the-ifttt-mobile-app) + + + +**Table of Contents** + +- [Nightscout/IFTTT Maker](#nightscoutifttt-maker) + - [Overview](#overview) + - [Events](#events) + - [Creating an Applet](#creating-an-applet) + - [1. Choose a Service](#1.-Choose-a-Service) + - [2. Choose a Trigger](#2.-Choose-a-Trigger) + - [3. Complete the Trigger field](#3.-Complete-the-Trigger-field) + - [4. Create an Action](#4.-Create-an-Action) + - [5. Complete Action Fields](#5.-Complete-Action-Fields) + - [6. Review and Finish](#6.-Review-and-Finish) + - [7. Get your Maker Key](#7.-Get-your-Maker-Key) + - [8. Configure your Nightscout site](#8.-Configure-your-Nightscout-site) + - [9. Configure the IFTTT mobile app](#9.-Configure-the-IFTTT-mobile-app) + +Nightscout/IFTTT Maker +====================================== + +## Overview + +In addition to Nightscout's web-based alarms, your site can also trigger push notifications (or other actions) through the [IFTTT Maker](https://ifttt.com/maker) service. With Maker you are able to integrate with all the other [IFTTT Services](https://ifttt.com/channels). For example, you can send a tweet when there is an alarm, change the color of a smart light, send an email, send a text, and much more. + +#### Note: There have been some recent reports of the IFTTT service delaying Nightscout alarms. Be sure to test your implementation before relying solely on its alerts. [Pushover](https://github.com/nightscout/cgm-remote-monitor/blob/dev/README.md#pushover) is an alternate push notification service that might be worth considering as well. + +## Events + + Plugins can create custom events, but all events sent to Maker will be prefixed with `ns-`. The core events are: + + * `ns-event` - This event is sent on *all* alarms and notifications. This is good catch-all event for general logging. + * `ns-allclear` - This event is sent when an alarm has been acknowledged or when the server starts up without triggering any alarms. (For example, you could use this event to turn a light to green.) + * `ns-info` - All notifications at the `info` level will cause this event to be triggered. + * `ns-warning` - All notifications at the `warning` level will cause this event to be triggered. + * `ns-urgent` - All notifications at the `urgent` level will cause this event to be triggered. + * `ns-warning-high` - This event is triggered when crossing the `BG_TARGET_TOP` threshold. + * `ns-urgent-high` - This event is triggered when crossing the `BG_HIGH` threshold. + * `ns-warning-low` - This event is triggered when crossing the `BG_TARGET_BOTTOM` threshold. + * `ns-urgent-low` - This event is triggered when crossing the `BG_LOW` threshold. + * `ns-info-treatmentnotify` - This event is triggered when a treatment is entered into the `careportal`. + * `ns-warning-bwp` - This event is triggered when the `bwp` plugin generates a warning alarm. + * `ns-urgent-bwp` - This event is triggered when the `bwp` plugin generates an urgent alarm. + +## Creating an Applet +Set up an [IFTTT](https://ifttt.com/) account, and log into it. + +### 1. Choose a Service +On the "My Applets" page, click "New Applet", then click the blue `+this`. Search for "webhooks" among the services, and click it. + +![service_search](./maker-setup-images/service_search.jpg) + +### 2. Choose a Trigger +Click on the "Receive a web request" box. + +### 3. Complete the Trigger field +Enter one of the Nightscout events listed above (like `ns-urgent-low`), and click "Create Trigger". + +![set_trigger](./maker-setup-images/set_trigger.jpg) + +### 4. Create an Action +Click on the blue `+that`. + +![blue_that](./maker-setup-images/blue_that.jpg) + +Search for the action you'd like this event to trigger. In this example, we'll choose the `Notifications` action to send a push notification. + +![action_search](./maker-setup-images/action_search.jpg) + +Choose the "Send a notification from the IFTTT app" action type for a basic push alert. You can experiment with the "rich" notifications later. + +### 5. Complete Action Fields +Enter the message that will display in this push notification. In this example, it was triggered on an `ns-urgent-low`, so we'll write something like "Urgent Low!". We can also display the current BG by including the `Value2` ingredient (via the "Add ingredient" button). + +Click the "Create action" button when you're done. + +![notification_message](./maker-setup-images/notification_message.jpg) + +### 6. Review and Finish +Click the "Finish" button if your applet looks good. + +### 7. Get your Maker Key + +Click on "My Applets" in the main menu, then click the "Services" tab, then search for "Webhooks" and select it. + +![service_settings_search](./maker-setup-images/service_settings_search.jpg) + +Go to the `Settings` for this service, in the upper right. + +![webhooks_settings](./maker-setup-images/webhooks_settings.jpg) + +The string of characters at the end of the URL here is your `MAKER_KEY`. Copy it from there, so we can paste it into your Config Vars. + +![maker_key](./maker-setup-images/maker_key.jpg) + +### 8. Configure your Nightscout site +From your [Heroku dashboard](https://dashboard.heroku.com), go to your app's Settings page, then click the "Reveal Config Vars" button. Find the `MAKER_KEY` entry, and edit its value, pasting in your Maker Key. If you don't already have a `MAKER_KEY` line, add it to the bottom of the list. + +![config_vars_maker](./maker-setup-images/config_vars_maker.jpg) + +Find your `ENABLE` line, and add `maker` to the list of enabled plugins. + +![config_vars_enable](./maker-setup-images/config_vars_enable.jpg) + +### 9. Configure the IFTTT mobile app +That's all of the services complete. In order to receive push notifications on a mobile device, you'll need to have the IFTTT app installed and logged into the same account you set up the actions in. + +To add more alerts for different events, just create a new applet for each trigger. + diff --git a/env.js b/env.js index d6d1f89d0cd..0d8d41409b0 100644 --- a/env.js +++ b/env.js @@ -21,11 +21,13 @@ function config ( ) { * See README.md for info about all the supported ENV VARs */ env.DISPLAY_UNITS = readENV('DISPLAY_UNITS', 'mg/dl'); + + console.log('Units set to', env.DISPLAY_UNITS ); + env.PORT = readENV('PORT', 1337); env.HOSTNAME = readENV('HOSTNAME', null); env.IMPORT_CONFIG = readENV('IMPORT_CONFIG', null); env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); - env.swagger_files = readENV('NIGHTSCOUT_SWAGGER_FILES', __dirname + '/node_modules/swagger-ui-dist/'); env.debug = { minify: readENVTruthy('DEBUG_MINIFY', true) }; @@ -56,6 +58,13 @@ function setSSL() { env.ca = fs.readFileSync(env.SSL_CA); } } + + env.insecureUseHttp = readENVTruthy("INSECURE_USE_HTTP", false); + env.secureHstsHeader = readENVTruthy("SECURE_HSTS_HEADER", true); + env.secureHstsHeaderIncludeSubdomains = readENVTruthy("SECURE_HSTS_HEADER_INCLUDESUBDOMAINS", false); + env.secureHstsHeaderPreload= readENVTruthy("SECURE_HSTS_HEADER_PRELOAD", false); + env.secureCsp = readENVTruthy("SECURE_CSP", false); + env.secureCspReportOnly = readENVTruthy("SECURE_CSP_REPORT_ONLY", false); } // A little ugly, but we don't want to read the secret into a var @@ -92,23 +101,10 @@ function setVersion() { function setStorage() { env.storageURI = readENV('STORAGE_URI') || readENV('MONGO_CONNECTION') || readENV('MONGO') || readENV('MONGOLAB_URI') || readENV('MONGODB_URI'); env.entries_collection = readENV('ENTRIES_COLLECTION') || readENV('MONGO_COLLECTION', 'entries'); - env.MQTT_MONITOR = readENV('MQTT_MONITOR', null); - if (env.MQTT_MONITOR) { - var hostDbCollection = [env.storageURI.split('mongodb://').pop().split('@').pop(), env.entries_collection].join('/'); - var mongoHash = crypto.createHash('sha1'); - mongoHash.update(hostDbCollection); - //some MQTT servers only allow the client id to be 23 chars - env.mqtt_client_id = mongoHash.digest('base64').substring(0, 23); - console.info('Using Mongo host/db/collection to create the default MQTT client_id', hostDbCollection); - if (env.MQTT_MONITOR.indexOf('?clientId=') === -1) { - console.info('Set MQTT client_id to: ', env.mqtt_client_id); - } else { - console.info('MQTT configured to use a custom client id, it will override the default: ', env.mqtt_client_id); - } - } env.authentication_collections_prefix = readENV('MONGO_AUTHENTICATION_COLLECTIONS_PREFIX', 'auth_'); env.treatments_collection = readENV('MONGO_TREATMENTS_COLLECTION', 'treatments'); env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile'); + env.settings_collection = readENV('MONGO_SETTINGS_COLLECTION', 'settings'); env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus'); env.food_collection = readENV('MONGO_FOOD_COLLECTION', 'food'); env.activity_collection = readENV('MONGO_ACTIVITY_COLLECTION', 'activity'); @@ -141,8 +137,6 @@ function updateSettings() { env.settings.authDefaultRoles = env.settings.authDefaultRoles || ""; env.settings.authDefaultRoles += ' careportal'; } - - } function readENV(varName, defaultValue) { @@ -152,6 +146,13 @@ function readENV(varName, defaultValue) { || process.env[varName] || process.env[varName.toLowerCase()]; + if (varName == 'DISPLAY_UNITS' && value) { + if (value.toLowerCase().includes('mmol')) { + value = 'mmol'; + } else { + value = 'mg/dl'; + } + } return value != null ? value : defaultValue; } @@ -159,7 +160,8 @@ function readENV(varName, defaultValue) { function readENVTruthy(varName, defaultValue) { var value = readENV(varName, defaultValue); if (typeof value === 'string' && (value.toLowerCase() === 'on' || value.toLowerCase() === 'true')) { value = true; } - if (typeof value === 'string' && (value.toLowerCase() === 'off' || value.toLowerCase() === 'false')) { value = false; } + else if (typeof value === 'string' && (value.toLowerCase() === 'off' || value.toLowerCase() === 'false')) { value = false; } + else { value=defaultValue } return value; } @@ -193,6 +195,6 @@ function findExtendedSettings (envs) { } }); return extended; -} + } module.exports = config; diff --git a/lib/admin_plugins/cleanentriesdb.js b/lib/admin_plugins/cleanentriesdb.js new file mode 100644 index 00000000000..3793eef8ffd --- /dev/null +++ b/lib/admin_plugins/cleanentriesdb.js @@ -0,0 +1,80 @@ +'use strict'; + +var moment = require('moment'); + +var cleanentriesdb = { + name: 'cleanentriesdb' + , label: 'Clean Mongo entries (glucose entries) database' + , pluginType: 'admin' +}; + +function init() { + return cleanentriesdb; +} + +module.exports = init; + +cleanentriesdb.actions = [ + { + name: 'Delete all documents from entries collection older than 180 days' + , description: 'This task removes all documents from entries collection that are older than 180 days. Useful when uploader battery status is not properly updated.' + , buttonLabel: 'Delete old documents' + , confirmText: 'Delete old documents from entries collection?' + , preventClose: true + } + ]; + +cleanentriesdb.actions[0].init = function init(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + cleanentriesdb.name + '_0_status'); + + $status.hide(); + + var numDays = '
' + + ''; + + $('#admin_' + cleanentriesdb.name + '_0_html').html(numDays); + + if (callback) { callback(); } +}; + +cleanentriesdb.actions[0].code = function deleteOldRecords(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + cleanentriesdb.name + '_0_status'); + var numDays = Number($('#admin_entries_days').val()); + + if (isNaN(numDays) || (numDays < 3)) { + alert(translate('%1 is not a valid number - must be more than 2', { params: [$('#admin_entries_days').val()] })); + if (callback) { callback(); } + return; + } + var endDate = moment().subtract(numDays, 'days'); + + 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('/api/v1/entries/?find[date][$lte]=' + endDate.valueOf(), { + method: 'DELETE' + , headers: client.headers() + , success: function (retVal) { + $status.hide().text(translate('%1 records deleted',{ params: [retVal.n] })).fadeIn('slow'); + } + , error: function () { + $status.hide().text(translate('Error')).fadeIn('slow'); + } + }).done(function success () { + if (callback) { callback(); } + }).fail(function fail() { + if (callback) { callback(); } + }); + +}; diff --git a/lib/admin_plugins/cleanstatusdb.js b/lib/admin_plugins/cleanstatusdb.js index 4769a724789..29fb99bae16 100644 --- a/lib/admin_plugins/cleanstatusdb.js +++ b/lib/admin_plugins/cleanstatusdb.js @@ -1,58 +1,67 @@ 'use strict'; +var moment = require('moment'); + var cleanstatusdb = { name: 'cleanstatusdb' , label: 'Clean Mongo status database' , pluginType: 'admin' }; -function init() { +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?' + { + 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?' + } + , { + name: 'Delete all documents from devicestatus collection older than 30 days' + , description: 'This task removes all documents from devicestatus collection that are older than 30 days. Useful when uploader battery status is not properly updated.' + , buttonLabel: 'Delete old documents' + , confirmText: 'Delete old documents from devicestatus collection?' + , preventClose: true } ]; -cleanstatusdb.actions[0].init = function init(client, callback) { +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', { headers: client.headers() - , success: function (records) { + , success: function(records) { var recs = (records.length === 500 ? '500+' : records.length); - $status.hide().text(translate('Database contains %1 records',{ params: [recs] })).fadeIn('slow'); + $status.hide().text(translate('Database contains %1 records', { params: [recs] })).fadeIn('slow'); } - , error: function () { + , error: function() { $status.hide().text(translate('Error loading database')).fadeIn('slow'); } - }).done(function () { if (callback) { callback(); } }); + }).done(function() { if (callback) { callback(); } }); }; -cleanstatusdb.actions[0].code = function deleteRecords(client, 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' + method: 'DELETE' , url: '/api/v1/devicestatus/*' , headers: client.headers() }).done(function success () { @@ -60,10 +69,65 @@ cleanstatusdb.actions[0].code = function deleteRecords(client, callback) { if (callback) { callback(); } - }).fail(function fail() { + }).fail(function fail () { $status.hide().text(translate('Error')).fadeIn('slow'); if (callback) { callback(); } }); }; + +cleanstatusdb.actions[1].init = function init (client, callback) { + var translate = client.translate; + var $status = $('#admin_' + cleanstatusdb.name + '_1_status'); + + $status.hide(); + + var numDays = '
' + + ''; + + $('#admin_' + cleanstatusdb.name + '_1_html').html(numDays); + + if (callback) { callback(); } +}; + +cleanstatusdb.actions[1].code = function deleteOldRecords (client, callback) { + var translate = client.translate; + var $status = $('#admin_' + cleanstatusdb.name + '_1_status'); + var numDays = Number($('#admin_devicestatus_days').val()); + + if (isNaN(numDays) || (numDays < 1)) { + alert(translate('%1 is not a valid number', { params: [$('#admin_devicestatus_days').val()] })); + if (callback) { callback(); } + return; + } + var endDate = moment().subtract(numDays, 'days'); + var dateStr = endDate.format('YYYY-MM-DD'); + + 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('/api/v1/devicestatus/?find[created_at][$lte]=' + dateStr, { + method: 'DELETE' + , headers: client.headers() + , success: function(retVal) { + $status.text(translate('%1 records deleted', { params: [retVal.n] })); + } + , error: function() { + $status.hide().text(translate('Error')).fadeIn('slow'); + } + }).done(function success () { + if (callback) { callback(); } + }).fail(function fail () { + if (callback) { callback(); } + }); +}; diff --git a/lib/admin_plugins/cleantreatmentsdb.js b/lib/admin_plugins/cleantreatmentsdb.js new file mode 100644 index 00000000000..a8cc725fd82 --- /dev/null +++ b/lib/admin_plugins/cleantreatmentsdb.js @@ -0,0 +1,81 @@ +'use strict'; + +var moment = require('moment'); + +var cleantreatmentsdb = { + name: 'cleantreatmentsdb' + , label: 'Clean Mongo treatments database' + , pluginType: 'admin' +}; + +function init() { + return cleantreatmentsdb; +} + +module.exports = init; + +cleantreatmentsdb.actions = [ + { + name: 'Delete all documents from treatments collection older than 180 days' + , description: 'This task removes all documents from treatments collection that are older than 180 days. Useful when uploader battery status is not properly updated.' + , buttonLabel: 'Delete old documents' + , confirmText: 'Delete old documents from treatments collection?' + , preventClose: true + } + ]; + +cleantreatmentsdb.actions[0].init = function init(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + cleantreatmentsdb.name + '_0_status'); + + $status.hide(); + + var numDays = '
' + + ''; + + $('#admin_' + cleantreatmentsdb.name + '_0_html').html(numDays); + + if (callback) { callback(); } +}; + +cleantreatmentsdb.actions[0].code = function deleteOldRecords(client, callback) { + var translate = client.translate; + var $status = $('#admin_' + cleantreatmentsdb.name + '_0_status'); + var numDays = Number($('#admin_treatments_days').val()); + + if (isNaN(numDays) || (numDays < 3)) { + alert(translate('%1 is not a valid number - must be more than 2', { params: [$('#admin_treatments_days').val()] })); + if (callback) { callback(); } + return; + } + var endDate = moment().subtract(numDays, 'days'); + var dateStr = endDate.format('YYYY-MM-DD'); + + 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('/api/v1/treatments/?find[created_at][$lte]=' + dateStr, { + method: 'DELETE' + , headers: client.headers() + , success: function (retVal) { + $status.hide().text(translate('%1 records deleted',{ params: [retVal.n] })).fadeIn('slow'); + } + , error: function () { + $status.hide().text(translate('Error')).fadeIn('slow'); + } + }).done(function success () { + if (callback) { callback(); } + }).fail(function fail() { + if (callback) { callback(); } + }); + +}; diff --git a/lib/admin_plugins/futureitems.js b/lib/admin_plugins/futureitems.js index 3d5acc23099..4ea613a53c2 100644 --- a/lib/admin_plugins/futureitems.js +++ b/lib/admin_plugins/futureitems.js @@ -6,164 +6,164 @@ var futureitems = { , pluginType: 'admin' }; -function init() { +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 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' + + , { + 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) { +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))) + 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'))) + + 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'); + $status.hide().text(translate('Database contains %1 future records', { params: [records.length] })).fadeIn('slow'); + var table = $('').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 () { + , error: function() { $status.hide().text(translate('Error loading database')).fadeIn('slow'); futureitems.treatmentrecords = []; } - }).done(function () { if (callback) { callback(); } }); + }).done(function() { if (callback) { callback(); } }); }; -futureitems.actions[0].code = function deleteRecords(client, 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' + method: 'DELETE' , url: '/api/v1/treatments/' + _id , headers: client.headers() }).done(function success () { $status.text(translate('Record %1 removed ...', { params: [_id] })); - }).fail(function fail() { - $status.text(translate('Error removing record %1', { 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) { +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 + '&count=288', { headers: client.headers() - , success: function (records) { + , success: function(records) { futureitems.entriesrecords = records; - $status.hide().text(translate('Database contains %1 future records',{ params: [records.length] })).fadeIn('slow'); + $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 () { + , error: function() { $status.hide().text(translate('Error loading database')).fadeIn('slow'); futureitems.entriesrecords = []; } - }).done(function () { if (callback) { callback(); } }); + }).done(function() { if (callback) { callback(); } }); }; -futureitems.actions[1].code = function deleteRecords(client, 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' + method: 'DELETE' , url: '/api/v1/entries/' + _id , headers: client.headers() }).done(function success () { $status.text(translate('Record %1 removed ...', { params: [_id] })); - }).fail(function fail() { - $status.text(translate('Error removing record %1', { 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 index c6463ecca8e..224054538d1 100644 --- a/lib/admin_plugins/index.js +++ b/lib/admin_plugins/index.js @@ -8,6 +8,8 @@ function init() { require('./subjects')() , require('./roles')() , require('./cleanstatusdb')() + , require('./cleantreatmentsdb')() + , require('./cleanentriesdb')() , require('./futureitems')() ]; @@ -84,4 +86,4 @@ function init() { } -module.exports = init; \ No newline at end of file +module.exports = init; diff --git a/lib/admin_plugins/roles.js b/lib/admin_plugins/roles.js index 42191751c4b..f99c4344449 100644 --- a/lib/admin_plugins/roles.js +++ b/lib/admin_plugins/roles.js @@ -1,12 +1,14 @@ 'use strict'; +const _ = require('lodash'); + var roles = { name: 'roles' , label: 'Roles - Groups of People, Devices, etc' , pluginType: 'admin' }; -function init() { +function init () { return roles; } @@ -20,13 +22,13 @@ roles.actions = [{ , init: function init (client, callback) { $status = $('#admin_' + roles.name + '_0_status'); $status.hide().text(client.translate('Loading database ...')).fadeIn('slow'); - var table = $('
').css('margin-top','10px'); + var table = $('
').css('margin-top', '10px'); $('#admin_' + roles.name + '_0_html').append(table).append(genDialog(client)); reload(client, callback); } , preventClose: true , code: function createNewRole (client, callback) { - var role = { }; + var role = {}; openDialog(role, client, callback); } }]; @@ -40,9 +42,9 @@ function createOrSaveRole (role, client, callback) { , url: '/api/v2/authorization/roles/' , headers: client.headers() , data: role - }).done(function success() { + }).done(function success () { reload(client, callback); - }).fail(function fail(err) { + }).fail(function fail (err) { console.error('Unable to ' + method + ' Role', err.responseText); window.alert(client.translate('Unable to %1 Role', { params: [method] })); if (callback) { @@ -56,9 +58,9 @@ function deleteRole (role, client, callback) { method: 'DELETE' , url: '/api/v2/authorization/roles/' + role._id , headers: client.headers() - }).done(function success() { + }).done(function success () { reload(client, callback); - }).fail(function fail(err) { + }).fail(function fail (err) { console.error('Unable to delete Role', err.responseText); window.alert(client.translate('Unable to delete Role')); if (callback) { @@ -70,16 +72,16 @@ function deleteRole (role, client, callback) { function reload (client, callback) { $.ajax({ method: 'GET' - , url:'/api/v2/authorization/roles' + , url: '/api/v2/authorization/roles' , headers: client.headers() }).done(function success (records) { roles.records = records; - $status.hide().text(client.translate('Database contains %1 roles',{ params: [records.length] })).fadeIn('slow'); + $status.hide().text(client.translate('Database contains %1 roles', { params: [records.length] })).fadeIn('slow'); showRoles(records, client); if (callback) { callback(); } - }).fail(function fail(err) { + }).fail(function fail (err) { $status.hide().text(client.translate('Error loading database')).fadeIn('slow'); roles.records = []; if (callback) { @@ -90,56 +92,57 @@ function reload (client, callback) { function genDialog (client) { var ret = - '' - ; + ''; return $(ret); } function openDialog (role, client) { - $( '#editroledialog' ).dialog({ + $('#editroledialog').dialog({ width: 360 , height: 360 - , buttons: [ - { text: client.translate('Save'), - class: 'leftButton', - click: function() { + , buttons: [ + { + text: client.translate('Save') + , class: 'leftButton' + , click: function() { role.name = $('#edrole_name').val(); role.permissions = _.chain($('#edrole_permissions').val().toLowerCase().split(/[;, ]/)) - .map(_.trim) - .reject(_.isEmpty) - .sort() - .value(); + .map(_.trim) + .reject(_.isEmpty) + .sort() + .value(); role.notes = $('#edrole_notes').val(); var self = this; delete role.autoGenerated; createOrSaveRole(role, client, function callback () { - $( self ).dialog('close'); + $(self).dialog('close'); }); } - }, - { text: client.translate('Cancel'), - click: function () { $( this ).dialog('close'); } + } + , { + text: client.translate('Cancel') + , click: function() { $(this).dialog('close'); } } ] - , open : function() { + , open: function() { $(this).parent().css('box-shadow', '20px 20px 20px 0px black'); - $(this).parent().find('.ui-dialog-buttonset' ).css({'width':'100%','text-align':'right'}); - $(this).parent().find('button:contains("'+client.translate('Save')+'")').css({'float':'left'}); + $(this).parent().find('.ui-dialog-buttonset').css({ 'width': '100%', 'text-align': 'right' }); + $(this).parent().find('button:contains("' + client.translate('Save') + '")').css({ 'float': 'left' }); $('#edrole_name').val(role.name || '').focus(); $('#edrole_permissions').val(role.permissions ? role.permissions.join(' ') : ''); $('#edrole_notes').val(role.notes || ''); @@ -151,14 +154,14 @@ function openDialog (role, client) { function showRole (role, table, client) { var editIcon = $(''); - editIcon.click(function clicked ( ) { + editIcon.click(function clicked () { openDialog(role, client); }); var deleteIcon = ''; if (role._id) { deleteIcon = $(''); - deleteIcon.click(function clicked() { + deleteIcon.click(function clicked () { var ok = window.confirm(client.translate('Are you sure you want to delete: ') + role.name); if (ok) { deleteRole(role, client); @@ -166,21 +169,21 @@ function showRole (role, table, client) { }); } - table.append($('').css('background-color','#0f0f0f') - .append($('').css('background-color', '#0f0f0f') + .append($('').css('background','#040404') - .append($('').css('background', '#040404') + .append($('').appendTo(table); var totalDailyInsulin = bolusInsulin + baseBasalInsulin + positiveTemps + negativeTemps; $('').appendTo(table); + + $('').appendTo(table); + $('').appendTo(table); + $('').appendTo(table); + $('').appendTo(table); $('#daytodaystatchart-' + day).append(table); var chartData = [ { - label: translate('Basal'), - count: totalBasalInsulin, - pct: (totalBasalInsulin / totalDailyInsulin * 100).toFixed(0) - }, - {label: translate('Bolus'), count: bolusInsulin, pct: (bolusInsulin / totalDailyInsulin * 100).toFixed(0)} + label: translate('Basal') + , count: totalBasalInsulin + , pct: (totalBasalInsulin / totalDailyInsulin * 100).toFixed(0) + } + , { label: translate('Bolus'), count: bolusInsulin, pct: (bolusInsulin / totalDailyInsulin * 100).toFixed(0) } ]; // Insulin distribution chart @@ -791,105 +934,111 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) var height = 120; var radius = Math.min(width, height) / 2; - var color = d3.scale.ordinal().range([basalcolor, boluscolor]); + var color = d3.scaleOrdinal().range([basalcolor, boluscolor]); - var labelArc = d3.svg.arc() - .outerRadius(radius / 2) - .innerRadius(radius / 2); + var labelArc = d3.arc() + .outerRadius(radius / 2) + .innerRadius(radius / 2); var svg = d3.select('#daytodaystatinsulinpiechart-' + day) - .append('svg') - .attr('width', width) - .attr('height', height) - .append('g') - .attr('transform', 'translate(' + (width / 2) + - ',' + (height / 2) + ')'); - - var arc = d3.svg.arc() - .outerRadius(radius); - - var pie = d3.layout.pie() - .value(function (d) { - return d.count; - }) - .sort(null); + .append('svg') + .attr('width', width) + .attr('height', height) + .append('g') + .attr('transform', 'translate(' + (width / 2) + + ',' + (height / 2) + ')'); + + var arc = d3.arc() + .innerRadius(0) + .outerRadius(radius); + + var pie = d3.pie() + .value(function(d) { + return d.count; + }) + .sort(null); var insulg = svg.selectAll('.insulinarc') - .data(pie(chartData)) - .enter() - .append('g') - .attr('class', 'insulinarc'); + .data(pie(chartData)) + .enter() + .append('g') + .attr('class', 'insulinarc'); insulg.append('path') - .attr('d', arc) - .attr('opacity', '0.5') - .attr('fill', function (d) { - return color(d.data.label); - }); + .attr('d', arc) + .attr('opacity', '0.5') + .attr('fill', function(d) { + return color(d.data.label); + }); insulg.append('text') - .attr('transform', function (d) { - return 'translate(' + labelArc.centroid(d) + ')'; - }) - .attr('dy', '.15em') - .style('font-weight', 'bold') - .attr('text-anchor', 'middle') - .text(function (d) { - return d.data.pct + '%'; - }); + .attr('transform', function(d) { + return 'translate(' + labelArc.centroid(d) + ')'; + }) + .attr('dy', '.15em') + .style('font-weight', 'bold') + .attr('text-anchor', 'middle') + .text(function(d) { + return d.data.pct + '%'; + }); // Carbs pie chart - var carbscolor = d3.scale.ordinal().range(['red']); + var carbscolor = d3.scaleOrdinal().range(['red']); var carbsData = [ - {label: translate('Carbs'), count: data.dailyCarbs} + { label: translate('Carbs'), count: data.dailyCarbs } ]; var carbssvg = d3.select('#daytodaystatcarbspiechart-' + day) - .append('svg') - .attr('width', width) - .attr('height', height) - .append('g') - .attr('transform', 'translate(' + (width / 2) + - ',' + (height / 2) + ')'); - - var carbsarc = d3.svg.arc() - .outerRadius(radius * data.dailyCarbs / options.maxDailyCarbsValue); - - var carbspie = d3.layout.pie() - .value(function (d) { - return d.count; - }) - .sort(null); + .append('svg') + .attr('width', width) + .attr('height', height) + .append('g') + .attr('transform', 'translate(' + (width / 2) + + ',' + (height / 2) + ')'); + + var carbsarc = d3.arc() + .outerRadius(radius * data.dailyCarbs / options.maxDailyCarbsValue); + + var carbspie = d3.pie() + .value(function(d) { + return d.count; + }) + .sort(null); var carbsg = carbssvg.selectAll('.carbsarc') - .data(carbspie(carbsData)) - .enter() - .append('g') - .attr('class', 'carbsarc'); + .data(carbspie(carbsData)) + .enter() + .append('g') + .attr('class', 'carbsarc'); carbsg.append('path') - .attr('d', carbsarc) - .attr('opacity', '0.5') - .attr('fill', function (d) { - return carbscolor(d.data.label); - }); + .attr('d', carbsarc) + .attr('opacity', '0.5') + .attr('fill', function(d) { + return carbscolor(d.data.label); + }); carbsg.append('text') - .attr('transform', function () { - return 'translate(0,0)'; - }) - .attr('dy', '.15em') - .style('font-weight', 'bold') - .attr('text-anchor', 'middle') - .text(function (d) { - return d.data.count + 'g'; - }); + .attr('transform', function() { + return 'translate(0,0)'; + }) + .attr('dy', '.15em') + .style('font-weight', 'bold') + .attr('text-anchor', 'middle') + .text(function(d) { + return d.data.count + 'g'; + }); } tddSum += totalDailyInsulin; + basalSum += totalBasalInsulin; + baseBasalSum += baseBasalInsulin; + bolusSum += bolusInsulin; carbsSum += data.dailyCarbs; + proteinSum += data.dailyProtein; + fatSum += data.dailyFat; appendProfileSwitch(context, { //eventType: 'Profile Switch' @@ -898,12 +1047,15 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) , first: true }); - function appendProfileSwitch(context, treatment) { + function appendProfileSwitch (context, treatment) { + + if (!treatment.cutting && !treatment.profile) { return; } + var sign = treatment.first ? '▲▲▲' : '▬▬▬'; var text; if (treatment.cutting) { text = sign + ' ' + client.profilefunctions.profileSwitchName(treatment.cutting) + ' ' + '►►►' + ' ' + client.profilefunctions.profileSwitchName(treatment.profile) + ' ' + sign; - } else { + } else { text = sign + ' ' + client.profilefunctions.profileSwitchName(treatment.profile) + ' ' + sign; } context.append('text') @@ -912,7 +1064,7 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) .attr('fill', '#0099ff') .attr('text-anchor', 'start') .attr('dy', '.35em') - .attr('transform', 'rotate(-90 ' + (xScale2(treatment.mills) + padding.left) + ',' + (yScaleBasals(0) + padding.top - 10) + ') ' + + .attr('transform', 'rotate(-90 ' + (xScale2(treatment.mills) + padding.left) + ',' + (yScaleBasals(0) + padding.top - 10) + ') ' + 'translate(' + (xScale2(treatment.mills) + padding.left) + ',' + (yScaleBasals(0) + padding.top - 10) + ')') .text(text); } @@ -920,9 +1072,7 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) console.log("Rendering " + day, new Date().getTime() - timestart.getTime(), "msecs"); } - function hideTooltip ( ) { - client.tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); + function hideTooltip () { + client.tooltip.style('opacity', 0); } }; diff --git a/lib/report_plugins/glucosedistribution.js b/lib/report_plugins/glucosedistribution.js index ad08c3084ff..f97bd034765 100644 --- a/lib/report_plugins/glucosedistribution.js +++ b/lib/report_plugins/glucosedistribution.js @@ -1,487 +1,484 @@ 'use strict'; +var consts = require('../constants'); + var glucosedistribution = { - name: 'glucosedistribution', - label: 'Distribution', - pluginType: 'report' + name: 'glucosedistribution' + , label: 'Distribution' + , pluginType: 'report' }; -function init() { - return glucosedistribution; +function init () { + return glucosedistribution; } module.exports = init; -glucosedistribution.html = function html(client) { - var translate = client.translate; - var ret = - '

' + - translate('Glucose distribution') + - ' (' + - ' ' + - ' )' + - '

' + - '
').attr('width','20%').append(editIcon).append(deleteIcon).append(role.name)) - .append($('').attr('width','20%').append(_.isEmpty(role.permissions) ? '[none]' : role.permissions.join(' '))) - .append($('').attr('width','10%').append(role._id ? (role.notes ? role.notes : '') : '[system default]')) + table.append($('
').attr('width', '20%').append(editIcon).append(deleteIcon).append(role.name)) + .append($('').attr('width', '20%').append(_.isEmpty(role.permissions) ? '[none]' : role.permissions.join(' '))) + .append($('').attr('width', '10%').append(role._id ? (role.notes ? role.notes : '') : '[system default]')) ); } function showRoles (roles, client) { var table = $('#admin_roles_table'); - table.empty().append($('
').css('width','100px').attr('align','left').append(client.translate('Name'))) - .append($('').css('width','150px').attr('align','left').append(client.translate('Permissions'))) - .append($('').css('width','150px').attr('align','left').append(client.translate('Notes'))) + table.empty().append($('
').css('width', '100px').attr('align', 'left').append(client.translate('Name'))) + .append($('').css('width', '150px').attr('align', 'left').append(client.translate('Permissions'))) + .append($('').css('width', '150px').attr('align', 'left').append(client.translate('Notes'))) ); - for (var t=0; t').css('margin-top','10px'); + var table = $('').css('margin-top', '10px'); $('#admin_' + subjects.name + '_0_html').append(table).append(genDialog(client)); reload(client, callback); } @@ -39,9 +41,9 @@ function createOrSaveSubject (subject, client, callback) { , url: '/api/v2/authorization/subjects/' , headers: client.headers() , data: subject - }).done(function success() { + }).done(function success () { reload(client, callback); - }).fail(function fail(err) { + }).fail(function fail (err) { console.error('Unable to ' + method + ' Subject', err.responseText); window.alert(client.translate('Unable to ' + method + ' Subject')); if (callback) { @@ -55,9 +57,9 @@ function deleteSubject (subject, client, callback) { method: 'DELETE' , url: '/api/v2/authorization/subjects/' + subject._id , headers: client.headers() - }).done(function success() { + }).done(function success () { reload(client, callback); - }).fail(function fail(err) { + }).fail(function fail (err) { console.error('Unable to delete Subject', err.responseText); window.alert(client.translate('Unable to delete Subject')); if (callback) { @@ -69,16 +71,16 @@ function deleteSubject (subject, client, callback) { function reload (client, callback) { $.ajax({ method: 'GET' - , url:'/api/v2/authorization/subjects' + , url: '/api/v2/authorization/subjects' , headers: client.headers() }).done(function success (records) { subjects.records = records; - $status.hide().text(client.translate('Database contains %1 subjects',{ params: [records.length] })).fadeIn('slow'); + $status.hide().text(client.translate('Database contains %1 subjects', { params: [records.length] })).fadeIn('slow'); showSubjects(records, client); if (callback) { callback(); } - }).fail(function fail(err) { + }).fail(function fail (err) { $status.hide().text(client.translate('Error loading database')).fadeIn('slow'); subjects.records = []; if (callback) { @@ -89,56 +91,57 @@ function reload (client, callback) { function genDialog (client) { var ret = - '' - ; + ''; return $(ret); } function openDialog (subject, client) { - $( '#editsubjectdialog' ).dialog({ + $('#editsubjectdialog').dialog({ width: 360 , height: 300 - , buttons: [ - { text: client.translate('Save'), - class: 'leftButton', - click: function() { + , buttons: [ + { + text: client.translate('Save') + , class: 'leftButton' + , click: function() { subject.name = $('#edsub_name').val(); subject.roles = _.chain($('#edsub_roles').val().toLowerCase().split(/[;, ]/)) - .map(_.trim) - .reject(_.isEmpty) - .sort() - .value(); + .map(_.trim) + .reject(_.isEmpty) + .sort() + .value(); subject.notes = $('#edsub_notes').val(); var self = this; - createOrSaveSubject(subject, client, function callback ( ) { - $( self ).dialog('close'); + createOrSaveSubject(subject, client, function callback () { + $(self).dialog('close'); }); } - }, - { text: client.translate('Cancel'), - click: function () { $( this ).dialog('close'); } + } + , { + text: client.translate('Cancel') + , click: function() { $(this).dialog('close'); } } ] - , open : function() { + , open: function() { $(this).parent().css('box-shadow', '20px 20px 20px 0px black'); - $(this).parent().find('.ui-dialog-buttonset' ).css({'width':'100%','text-align':'right'}); - $(this).parent().find('button:contains("'+client.translate('Save')+'")').css({'float':'left'}); + $(this).parent().find('.ui-dialog-buttonset').css({ 'width': '100%', 'text-align': 'right' }); + $(this).parent().find('button:contains("' + client.translate('Save') + '")').css({ 'float': 'left' }); $('#edsub_name').val(subject.name || '').focus(); $('#edsub_roles').val(subject.roles ? subject.roles.join(', ') : ''); $('#edsub_notes').val(subject.notes || ''); @@ -150,33 +153,33 @@ function openDialog (subject, client) { function showSubject (subject, table, client) { var editIcon = $(''); - editIcon.click(function clicked ( ) { + editIcon.click(function clicked () { openDialog(subject, client); }); var deleteIcon = $(''); - deleteIcon.click(function clicked ( ) { + deleteIcon.click(function clicked () { var ok = window.confirm(client.translate('Are you sure you want to delete: ') + subject.name); if (ok) { deleteSubject(subject, client); } }); - table.append($('').css('background-color','#0f0f0f') - .append($('').css('background-color', '#0f0f0f') + .append($('').css('background','#040404') - .append($('').css('background', '#040404') + .append($(''); $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); thead.appendTo(table); - sorteddaystoshow.forEach(function (day) { + sorteddaystoshow.forEach(function(day) { var tr = $(''); var daysRecords = datastorage[day].statsrecords; - + if (daysRecords.length === 0) { $('').appendTo(tr); - $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); table.append(tr); - return;; + return; } minForDay = daysRecords[0].sgv; @@ -101,53 +99,52 @@ dailystats.report = function report_dailystats(datastorage,sorteddaystoshow,opti sum += record.sgv; return out; }, { - lows: 0, - normal: 0, - highs: 0 + lows: 0 + , normal: 0 + , highs: 0 }); var average = sum / daysRecords.length; var bgValues = daysRecords.map(function(r) { return r.sgv; }); - $('').appendTo(tr); - - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); + $('').appendTo(tr); + + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); table.append(tr); var inrange = [ { - label: translate('Low'), - data: Math.round(stats.lows * 1000 / daysRecords.length) / 10 - }, - { - label: translate('In Range'), - data: Math.round(stats.normal * 1000 / daysRecords.length) / 10 - }, - { - label: translate('High'), - data: Math.round(stats.highs * 1000 / daysRecords.length) / 10 + label: translate('Low') + , data: Math.round(stats.lows * 1000 / daysRecords.length) / 10 + } + , { + label: translate('In Range') + , data: Math.round(stats.normal * 1000 / daysRecords.length) / 10 + } + , { + label: translate('High') + , data: Math.round(stats.highs * 1000 / daysRecords.length) / 10 } ]; $.plot( - '#dailystat-chart-' + day.toString(), - inrange, - { + '#dailystat-chart-' + day.toString() + , inrange, { series: { pie: { show: true } - }, - colors: ['#f88', '#8f8', '#ff8'] + } + , colors: ['#f88', '#8f8', '#ff8'] } ); }); diff --git a/lib/report_plugins/daytoday.js b/lib/report_plugins/daytoday.js index 1d6b6f84f40..f4b5da7cf45 100644 --- a/lib/report_plugins/daytoday.js +++ b/lib/report_plugins/daytoday.js @@ -11,80 +11,110 @@ var daytoday = { , pluginType: 'report' }; -function init() { +function init () { return daytoday; } module.exports = init; -daytoday.html = function html(client) { +daytoday.html = function html (client) { var translate = client.translate; var ret = - '

' + translate('Day to day') + '

' - + '' + translate('To see this report, press SHOW while in this view') + '
' - + translate('Display') + ': ' - + ''+translate('Insulin')+'' - + ''+translate('Carbs')+'' - + ''+translate('Basal rate')+'' - + ''+translate('Notes') - + ''+translate('Food') - + ''+translate('Raw')+'' - + ''+translate('IOB')+'' - + ''+translate('COB')+'' - + ''+translate('OpenAPS')+'' - + ''+translate('Insulin distribution')+'' - + ' '+translate('Size') - + ' ' - + '
' - + translate('Scale') + ': ' - + '' - + translate('Linear') - + '' - + translate('Logarithmic') - + '
' - + '
' - + '
' - ; - return ret; + '

' + translate('Day to day') + '

' + + '' + translate('To see this report, press SHOW while in this view') + '
' + + translate('Display') + ': ' + + '' + translate('Insulin') + '' + + '' + translate('Carbs') + '' + + '' + translate('Basal rate') + '' + + '' + translate('Notes') + + '' + translate('Food') + + '' + translate('Raw') + '' + + '' + translate('IOB') + '' + + '' + translate('COB') + '' + + '' + translate('Predictions') + '' + + '' + translate('OpenAPS') + '' + + '' + translate('Insulin distribution') + '' + + ' ' + translate('Size') + + ' ' + + '
' + + translate('Scale') + ': ' + + '' + + translate('Linear') + + '' + + translate('Logarithmic') + + '' + + '
' + + '
' + + '
'; + return ret; }; -daytoday.prepareHtml = function daytodayPrepareHtml(sorteddaystoshow) { +daytoday.prepareHtml = function daytodayPrepareHtml (sorteddaystoshow) { $('#daytodaycharts').html(''); - sorteddaystoshow.forEach(function eachDay(d) { + sorteddaystoshow.forEach(function eachDay (d) { $('#daytodaycharts').append($('
').attr('width','20%').append(editIcon).append(deleteIcon).append(subject.name)) - .append($('').attr('width','20%').append(subject.roles ? subject.roles.join(', ') : '[none]')) - .append($('').attr('width','20%').append('' + subject.accessToken + '')) - .append($('').attr('width','10%').append(subject.notes ? subject.notes : '')) + table.append($('
').attr('width', '20%').append(editIcon).append(deleteIcon).append(subject.name)) + .append($('').attr('width', '20%').append(subject.roles ? subject.roles.join(', ') : '[none]')) + .append($('').attr('width', '20%').append('' + subject.accessToken + '')) + .append($('').attr('width', '10%').append(subject.notes ? subject.notes : '')) ); } function showSubjects (subjects, client) { var table = $('#admin_subjects_table'); - table.empty().append($('
').css('width','100px').attr('align','left').append(client.translate('Name'))) - .append($('').css('width','150px').attr('align','left').append(client.translate('Roles'))) - .append($('').css('width','150px').attr('align','left').append(client.translate('Access Token'))) - .append($('').css('width','150px').attr('align','left').append(client.translate('Notes'))) + table.empty().append($('
').css('width', '100px').attr('align', 'left').append(client.translate('Name'))) + .append($('').css('width', '150px').attr('align', 'left').append(client.translate('Roles'))) + .append($('').css('width', '150px').attr('align', 'left').append(client.translate('Access Token'))) + .append($('').css('width', '150px').attr('align', 'left').append(client.translate('Notes'))) ); - for (var t=0; t 2) { - locale = locale.substr(0, 2); - } - ctx.language.set(locale); - moment.locale(locale); - } - - switch (req.body.request.type) { - case 'IntentRequest': - onIntent(req.body.request.intent, function (title, response) { - res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true')); - next( ); - }); - break; - case 'LaunchRequest': - onLaunch(req.body.request.intent, function (title, response) { - res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true')); - next( ); - }); - break; - case 'SessionEndedRequest': - onSessionEnded(req.body.request.intent, function (alexaResponse) { - res.json(alexaResponse); - next( ); - }); - break; - } - }); - - ctx.alexa.addToRollup('Status', function bgRollupHandler(slots, sbx, callback) { - entries.list({count: 1}, function (err, records) { - var direction; - if (translate(records[0].direction)) { - direction = translate(records[0].direction); - } else { - direction = records[0].direction; - } - var status = translate('alexaStatus', { - params: [ - sbx.scaleMgdl(records[0].sgv), - direction, - moment(records[0].date).from(moment(sbx.time)) - ] - }); - //var status = sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time)) + '.'; - callback(null, {results: status, priority: -1}); + var entries = ctx.entries; + var express = require('express') + , api = express.Router( ); + var translate = ctx.language.translate; + + // invoke common middleware + api.use(wares.sendJSONStatus); + // text body types get handled as raw buffer stream + api.use(wares.bodyParser.raw()); + // json body types get handled as parsed json + api.use(wares.bodyParser.json()); + + ctx.plugins.eachEnabledPlugin(function each(plugin){ + if (plugin.virtAsst) { + if (plugin.virtAsst.intentHandlers) { + console.log('Alexa: Plugin ' + plugin.name + ' supports Virtual Assistants'); + _each(plugin.virtAsst.intentHandlers, function (route) { + if (route) { + ctx.alexa.configureIntentHandler(route.intent, route.intentHandler, route.metrics); + } }); - // console.log('BG results called'); - // callback(null, 'BG results'); - }, 'BG Status'); - - ctx.alexa.configureIntentHandler('MetricNow', function ( callback, slots, sbx, locale) { - entries.list({count: 1}, function(err, records) { - var direction; - if(translate(records[0].direction)){ - direction = translate(records[0].direction); - } else { - direction = records[0].direction; - } - var status = translate('alexaStatus', { - params: [ - sbx.scaleMgdl(records[0].sgv), - direction, - moment(records[0].date).from(moment(sbx.time))] - }); - //var status = sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time)); - callback('Current blood glucose', status); + } + if (plugin.virtAsst.rollupHandlers) { + console.log('Alexa: Plugin ' + plugin.name + ' supports rollups for Virtual Assistants'); + _each(plugin.virtAsst.rollupHandlers, function (route) { + console.log('Route'); + console.log(route); + if (route) { + ctx.alexa.addToRollup(route.rollupGroup, route.rollupHandler, route.rollupName); + } }); - }, 'metric', ['bg', 'blood glucose', 'number']); - - ctx.alexa.configureIntentHandler('NSStatus', function(callback, slots, sbx, locale) { - ctx.alexa.getRollup('Status', sbx, slots, locale, function (status) { - callback('Full status', status); - }); - }); - - - function onLaunch() { - console.log('Session launched'); + } + } else { + console.log('Alexa: Plugin ' + plugin.name + ' does not support Virtual Assistants'); } - - function onIntent(intent, next) { - console.log('Received intent request'); - console.log(JSON.stringify(intent)); - handleIntent(intent.name, intent.slots, next); + }); + + api.post('/alexa', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) { + console.log('Incoming request from Alexa'); + var locale = req.body.request.locale; + if(locale){ + if(locale.length > 2) { + locale = locale.substr(0, 2); + } + ctx.language.set(locale); + moment.locale(locale); } - function onSessionEnded() { - console.log('Session ended'); + switch (req.body.request.type) { + case 'IntentRequest': + onIntent(req.body.request.intent, function (title, response) { + res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true')); + next( ); + }); + break; + case 'LaunchRequest': + onLaunch(req.body.request.intent, function (title, response) { + res.json(ctx.alexa.buildSpeechletResponse(title, response, '', 'true')); + next( ); + }); + break; + case 'SessionEndedRequest': + onSessionEnded(req.body.request.intent, function (alexaResponse) { + res.json(alexaResponse); + next( ); + }); + break; } + }); + + ctx.alexa.addToRollup('Status', function bgRollupHandler(slots, sbx, callback) { + entries.list({count: 1}, function (err, records) { + var direction; + if (translate(records[0].direction)) { + direction = translate(records[0].direction); + } else { + direction = records[0].direction; + } + var status = translate('virtAsstStatus', { + params: [ + sbx.scaleMgdl(records[0].sgv), + direction, + moment(records[0].date).from(moment(sbx.time)) + ] + }); + + callback(null, {results: status, priority: -1}); + }); + }, 'BG Status'); + + ctx.alexa.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { + entries.list({count: 1}, function(err, records) { + var direction; + if(translate(records[0].direction)){ + direction = translate(records[0].direction); + } else { + direction = records[0].direction; + } + var status = translate('virtAsstStatus', { + params: [ + sbx.scaleMgdl(records[0].sgv), + direction, + moment(records[0].date).from(moment(sbx.time))] + }); + + callback(translate('virtAsstTitleCurrentBG'), status); + }); + }, ['bg', 'blood glucose', 'number']); - function handleIntent(intentName, slots, next) { - var handler = ctx.alexa.getIntentHandler(intentName, slots); - if (handler){ - var sbx = initializeSandbox(); - handler(next, slots, sbx); - } else { - next('Unknown Intent', 'I\'m sorry I don\'t know what you\'re asking for'); - } + ctx.alexa.configureIntentHandler('NSStatus', function (callback, slots, sbx, locale) { + ctx.alexa.getRollup('Status', sbx, slots, locale, function (status) { + callback(translate('virtAsstTitleFullStatus'), status); + }); + }); + + + function onLaunch(intent, next) { + console.log('Session launched'); + console.log(JSON.stringify(intent)); + handleIntent(intent.name, intent.slots, next); + } + + function onIntent(intent, next) { + console.log('Received intent request'); + console.log(JSON.stringify(intent)); + handleIntent(intent.name, intent.slots, next); + } + + function onSessionEnded() { + console.log('Session ended'); + } + + function handleIntent(intentName, slots, next) { + var metric; + if (slots) { + if (slots.metric + && slots.metric.resolutions + && slots.metric.resolutions.resolutionsPerAuthority + && slots.metric.resolutions.resolutionsPerAuthority.length + && slots.metric.resolutions.resolutionsPerAuthority[0].status + && slots.metric.resolutions.resolutionsPerAuthority[0].status.code + && slots.metric.resolutions.resolutionsPerAuthority[0].status.code == "ER_SUCCESS_MATCH" + && slots.metric.resolutions.resolutionsPerAuthority[0].values + && slots.metric.resolutions.resolutionsPerAuthority[0].values.length + && slots.metric.resolutions.resolutionsPerAuthority[0].values[0].value + && slots.metric.resolutions.resolutionsPerAuthority[0].values[0].value.name + ){ + metric = slots.metric.resolutions.resolutionsPerAuthority[0].values[0].value.name; + } else { + next(translate('virtAsstUnknownIntentTitle'), translate('virtAsstUnknownIntentText')); + } } - function initializeSandbox() { - var sbx = require('../../sandbox')(); - sbx.serverInit(env, ctx); - ctx.plugins.setProperties(sbx); - return sbx; + var handler = ctx.alexa.getIntentHandler(intentName, metric); + if (handler){ + var sbx = initializeSandbox(); + handler(next, slots, sbx); + } else { + next(translate('virtAsstUnknownIntentTitle'), translate('virtAsstUnknownIntentText')); } + } + + function initializeSandbox() { + var sbx = require('../../sandbox')(); + sbx.serverInit(env, ctx); + ctx.plugins.setProperties(sbx); + return sbx; + } - return api; + return api; } -module.exports = configure; +module.exports = configure; \ No newline at end of file diff --git a/lib/api/const.json b/lib/api/const.json new file mode 100644 index 00000000000..cb1421d8520 --- /dev/null +++ b/lib/api/const.json @@ -0,0 +1,4 @@ +{ + "API1_VERSION": "1.0.0", + "API2_VERSION": "2.0.0" +} \ No newline at end of file diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 9d6fc1bfce7..91702902fa3 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -1,17 +1,18 @@ 'use strict'; -var consts = require('../../constants'); +const consts = require('../../constants'); +const moment = require('moment'); -function configure (app, wares, ctx) { - var express = require('express'), - api = express.Router( ); +function configure (app, wares, ctx, env) { + var express = require('express') + , api = express.Router(); // invoke common middleware api.use(wares.sendJSONStatus); // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw( )); + api.use(wares.bodyParser.raw()); // json body types get handled as parsed json - api.use(wares.bodyParser.json( )); + api.use(wares.bodyParser.json()); // also support url-encoded content-type api.use(wares.bodyParser.urlencoded({ extended: true })); @@ -23,7 +24,21 @@ function configure (app, wares, ctx) { if (!q.count) { q.count = 10; } - ctx.devicestatus.list(q, function (err, results) { + + ctx.devicestatus.list(q, function(err, results) { + + // Support date de-normalization for older clients + if (env.settings.deNormalizeDates) { + results.forEach(function(e) { + // eslint-disable-next-line no-prototype-builtins + if (e.created_at && e.hasOwnProperty('utcOffset')) { + const d = moment(e.created_at).utcOffset(e.utcOffset); + e.created_at = d.toISOString(true); + delete e.utcOffset; + } + }); + } + return res.json(results); }); }); @@ -32,7 +47,7 @@ function configure (app, wares, ctx) { function doPost (req, res) { var obj = req.body; - ctx.devicestatus.create(obj, function (err, created) { + ctx.devicestatus.create(obj, function(err, created) { if (err) { res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); } else { @@ -43,20 +58,55 @@ function configure (app, wares, ctx) { api.post('/devicestatus/', ctx.authorization.isPermitted('api:devicestatus:create'), doPost); - // delete record - api.delete('/devicestatus/:_id', ctx.authorization.isPermitted('api:devicestatus:delete'), function(req, res) { - ctx.devicestatus.remove(req.params._id, function (err, removed) { + /** + * @function delete_records + * Delete devicestatus. The query logic works the same way as find/list. This + * endpoint uses same search logic to remove records from the database. + */ + function delete_records (req, res, next) { + var query = req.query; + if (!query.count) { + query.count = 10 + } + + console.log('Delete records with query: ', query); + + // remove using the query + ctx.devicestatus.remove(query, function(err, stat) { if (err) { - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - } else { - res.json(removed); + console.log('devicestatus delete error: ', err); + return next(err); } + // yield some information about success of operation + res.json(stat); + + console.log('devicestatus records deleted'); + + return next(); }); - }); + } + api.delete('/devicestatus/:id', ctx.authorization.isPermitted('api:devicestatus:delete'), function(req, res, next) { + if (!req.query.find) { + req.query.find = { + _id: req.params.id + }; + } else { + req.query.find._id = req.params.id; + } + + if (req.query.find._id === '*') { + // match any record id + delete req.query.find._id; + } + next(); + }, delete_records); + + // delete record that match query + api.delete('/devicestatus/', ctx.authorization.isPermitted('api:devicestatus:delete'), delete_records); } - if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) { + if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/ ) { config_authed(app, api, wares, ctx); } @@ -64,4 +114,3 @@ function configure (app, wares, ctx) { } module.exports = configure; - diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 691cba7ecc0..faf922ff0e8 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -1,20 +1,21 @@ 'use strict'; -var _last = require('lodash/last'); -var _isNil = require('lodash/isNil'); -var _first = require('lodash/first'); -var _includes = require('lodash/includes'); - -var consts = require('../../constants'); -var es = require('event-stream'); -var sgvdata = require('sgvdata'); -var expand = require('expand-braces'); - -var ID_PATTERN = /^[a-f\d]{24}$/; - -function isId(value) { - //TODO: why did we need tht length check? - return value && ID_PATTERN.test(value) && value.length === 24; +const _last = require('lodash/last'); +const _isNil = require('lodash/isNil'); +const _first = require('lodash/first'); +const _includes = require('lodash/includes'); +const moment = require('moment'); + +const consts = require('../../constants'); +const es = require('event-stream'); +const braces = require('braces'); +const expand = braces.expand; + +const ID_PATTERN = /^[a-f\d]{24}$/; + +function isId (value) { + //TODO: why did we need tht length check? + return value && ID_PATTERN.test(value) && value.length === 24; } /** @@ -31,715 +32,739 @@ function isId(value) { * @param Object ctx The global ctx with all modules, storage, and event buses * configured. */ -function configure(app, wares, ctx) { - // default storage biased towards entries. - var entries = ctx.entries; - var express = require('express'), - api = express.Router(); - - // invoke common middleware - api.use(wares.sendJSONStatus); - // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw()); - // json body types get handled as parsed json - api.use(wares.bodyParser.json()); - // shortcut to use extension to specify output content-type - api.use(wares.extensions([ +function configure (app, wares, ctx, env) { + // default storage biased towards entries. + const entries = ctx.entries; + const express = require('express') + , api = express.Router(); + + // invoke common middleware + api.use(wares.sendJSONStatus); + // text body types get handled as raw buffer stream + api.use(wares.bodyParser.raw()); + // json body types get handled as parsed json + api.use(wares.bodyParser.json()); + // shortcut to use extension to specify output content-type + api.use(wares.extensions([ 'json', 'svg', 'csv', 'txt', 'png', 'html', 'tsv' ])); - // also support url-encoded content-type - api.use(wares.bodyParser.urlencoded({ - extended: true - })); - - api.use(ctx.authorization.isPermitted('api:entries:read')); + // also support url-encoded content-type + api.use(wares.bodyParser.urlencoded({ + extended: true + })); + + api.use(ctx.authorization.isPermitted('api:entries:read')); + /** + * @method force_typed_data + * @returns {Stream} Creates a through stream which validates that all + * elements on the stream have a `type` field. + * Generate a stream that ensures elements have a `type` field. + */ + function force_typed_data (opts) { /** - * @method force_typed_data - * @returns {Stream} Creates a through stream which validates that all - * elements on the stream have a `type` field. - * Generate a stream that ensures elements have a `type` field. + * @function sync + * Iterate over every element in the stream, enforcing some data type. */ - function force_typed_data(opts) { - /** - * @function sync - * Iterate over every element in the stream, enforcing some data type. - */ - function sync(data, next) { - // if element has no data type, but we know what the type should be - if (!data.type && opts.type) { - // bless absence with known type - data.type = opts.type; - } - // continue control flow to next element in the stream - next(null, data); + function sync (data, next) { + // if element has no data type, but we know what the type should be + if (!data.type && opts.type) { + // bless absence with known type + data.type = opts.type; + } + + // Support date de-normalization for older clients + if (env.settings.deNormalizeDates) { + // eslint-disable-next-line no-prototype-builtins + if (data.dateString && data.hasOwnProperty('utcOffset')) { + const d = moment(data.dateString).utcOffset(data.utcOffset); + data.dateString = d.toISOString(true); + delete data.utcOffset; } - // return configured stream - return es.map(sync); - } + } - // check for last modified from in-memory data - - function ifModifiedSinceCTX(req, res, next) { - - var lastEntry = _last(ctx.ddata.sgvs); - var lastEntryDate = null; - - if (!_isNil(lastEntry)) { - lastEntryDate = new Date(_last(ctx.ddata.sgvs).mills); - res.setHeader('Last-Modified', lastEntryDate.toUTCString()); - } + // continue control flow to next element in the stream + next(null, data); + } + // return configured stream + return es.map(sync); + } - var ifModifiedSince = req.get('If-Modified-Since'); - if (!ifModifiedSince) { - return next(); - } + // check for last modified from in-memory data - console.log("CGM Entry request with If-Modified-Since: ", ifModifiedSince); + function ifModifiedSinceCTX (req, res, next) { - if (lastEntryDate.getTime() <= new Date(ifModifiedSince).getTime()) { - res.status(304).send({ - status: 304, - message: 'Not modified', - type: 'internal' - }); - return; - } + var lastEntry = _last(ctx.ddata.sgvs); + var lastEntryDate = null; - return next(); + if (!_isNil(lastEntry)) { + lastEntryDate = new Date(_last(ctx.ddata.sgvs).mills); + res.setHeader('Last-Modified', lastEntryDate.toUTCString()); } - /** - * @method format_entries - * A final middleware to send payloads assembled by previous middlewares - * out to the http client. - * We expect a payload to be attached to `res.entries`. - // Middleware to format any response involving entries. - */ - function format_entries(req, res) { - // deduce what type of records we might expect - var type_params = { - type: (req.query && req.query.find && req.query.find.type && - req.query.find.type !== req.params.model) ? - req.query.find.type : req.params.model - }; - // prepare a stream of elements from some prepared payload - var output = es.readArray(res.entries || []); - // on other hand, if there's been some error, report that - if (res.entries_err) { - return res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', res.entries_err); - } + var ifModifiedSince = req.get('If-Modified-Since'); + if (!ifModifiedSince) { + return next(); + } - // IF-Modified-Since support + console.log('CGM Entry request with If-Modified-Since: ', ifModifiedSince); - function compare(a, b) { - var a_field = a.mills ? a.mills : a.date; - var b_field = b.mills ? b.mills : b.date; + if (lastEntryDate.getTime() <= Date.parse(ifModifiedSince)) { + console.log('Sending Not Modified'); + res.status(304).send({ + status: 304 + , message: 'Not modified' + , type: 'internal' + }); + return; + } - if (a_field > b_field) - return -1; - if (a_field < b_field) - return 1; - return 0; - } + return next(); + } + + /** + * @method format_entries + * A final middleware to send payloads assembled by previous middlewares + * out to the http client. + * We expect a payload to be attached to `res.entries`. + // Middleware to format any response involving entries. + */ + function format_entries (req, res) { + // deduce what type of records we might expect + var type_params = { + type: (req.query && req.query.find && req.query.find.type && + req.query.find.type !== req.params.model) ? + req.query.find.type : req.params.model + }; + + // f there's been some error, report that + if (res.entries_err) { + return res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', res.entries_err); + } - res.entries.sort(compare); + // IF-Modified-Since support - var lastEntry = _first(res.entries); - var lastEntryDate = null; + function compare (a, b) { + var a_field = a.mills ? a.mills : a.date; + var b_field = b.mills ? b.mills : b.date; - if (!_isNil(lastEntry)) { - if (lastEntry.mills) lastEntryDate = new Date(lastEntry.mills); - if (!lastEntry.mills && lastEntry.date) lastEntryDate = new Date(lastEntry.date); - res.setHeader('Last-Modified', lastEntryDate.toUTCString()); - } + if (a_field > b_field) + return -1; + if (a_field < b_field) + return 1; + return 0; + } - var ifModifiedSince = req.get('If-Modified-Since'); + res.entries.sort(compare); - console.log('If-Modified-Since: ' + new Date(ifModifiedSince) + ' Last-Modified', lastEntryDate); + var lastEntry = _first(res.entries); + var lastEntryDate = null; - if (lastEntryDate !== null && ifModifiedSince !== null && lastEntryDate.getTime() <= new Date(ifModifiedSince).getTime()) { - res.status(304).send({ - status: 304, - message: 'Not modified', - type: 'internal' - }); - return; - } + // prepare a stream of elements from some prepared payload + var output = es.readArray(res.entries || []); - // if no error, format the payload - // The general pattern here is to create an output stream that reformats - // the data correctly into the desired representation. - // The stream logic allows some streams to ensure that some basic rules, - // such as enforcing a type property to exist, are followed. - return res.format({ - text: function() { - res.set('Content-Type', 'text/plain'); - // sgvdata knows how to format sgv entries as text - es.pipeline(output, sgvdata.format(), es.writeArray(function(err, out) { - res.send(out.join('')); - })); - }, - csv: function() { - // sgvdata knows how to format sgv entries as text - res.set('Content-Type', 'text/plain'); - var csvpipe = require('sgvdata/lib/text')({ - format: ',', - parse: /[\t,]/ - }); - es.pipeline( - output, sgvdata.mapper(csvpipe.format), es.join('\n'), es.writeArray(function(err, out) { - res.send(out.join('')); - }) - ); - }, - json: function() { - // so long as every element has a `type` field, and some kind of - // date, we'll consider it valid - es.pipeline(output, force_typed_data(type_params), es.writeArray(function(err, out) { - res.json(out); - })); - } - }); + if (!_isNil(lastEntry)) { + if (lastEntry.mills) lastEntryDate = new Date(lastEntry.mills); + if (!lastEntry.mills && lastEntry.date) lastEntryDate = new Date(lastEntry.date); + res.setHeader('Last-Modified', lastEntryDate.toUTCString()); } - /** - * @method insert_entries - * middleware to process "uploads" of sgv data - * This inspects the http requests's incoming payload. This creates a - * validating stream for the appropriate type of payload, which is piped - * into the configured storage layer, saving the results in mongodb. - */ - // middleware to process "uploads" of sgv data - function insert_entries(req, res, next) { - // list of incoming records - var incoming = []; - // Potentially a single json encoded body. - // This can happen from either an url-encoded or json content-type. - if ('date' in req.body) { - // add it to the incoming list - incoming.push(req.body); - } - // potentially a list of json entries - if (req.body.length) { - // add them to the list - incoming = incoming.concat(req.body); - } + var ifModifiedSince = req.get('If-Modified-Since'); - /** - * @function inputs - * @returns {ReadableStream} Readable stream with all incoming elements - * in the stream. - * in node, pipe is the most interoperable interface - * inputs returns a readable stream representing all the potential - * records from the HTTP body. - * Most content-types are handled by express middeware. - * However, text/* types are given to us as a raw buffer, this - * function switches between these two variants to find the - * correct input stream. - * stream, so use svgdata to handle those. - * The inputs stream always emits sgv json objects. - */ - function inputs() { - var input; - // handle all text types - if (req.is('text/*')) { - // re-use the svgdata parsing stream - input = es.pipeline(req, sgvdata.parse()); - return input; - } - // use established list - return es.readArray(incoming); - } - - /** - * @function persist - * @returns {WritableStream} a writable persistent storage stream - * Sends stream elements into storage layer. - * Configures the storage layer stream. - */ - function persist(fn) { - if (req.persist_entries) { - // store everything - return entries.persist(fn); - } - // support a preview mode, just lint everything - return es.pipeline(entries.map(), es.writeArray(fn)); - } - - /** - * @function done - * Final callback store results on `res.entries`, after all I/O is done. - * store results and move to the next middleware - */ - function done(err, result) { - // assign payload - res.entries = result; - res.entries_err = err; - return next(); - } - - // pipe everything to persistent storage - // when finished, pass to the next piece of middleware - es.pipeline(inputs(), persist(done)); + if (lastEntryDate !== null && ifModifiedSince !== null && lastEntryDate.getTime() <= new Date(ifModifiedSince).getTime()) { + console.log('If-Modified-Since: ' + new Date(ifModifiedSince) + ' Last-Modified', lastEntryDate); + res.status(304).send({ + status: 304 + , message: 'Not modified' + , type: 'internal' + }); + return; } - /** - * @function prepReqModel - * @param {Request} req The request to inspect - * @param {String} model The name of the model to use if not found. - * Sets `req.query.find.type` to your chosen model. - */ - function prepReqModel(req, model) { - var type = model || 'sgv'; - if (!req.query.find) { - req.query.find = { - type: type - }; - } else { - req.query.find.type = type; - } + function formatWithSeparator (data, separator) { + if (data === null || data.constructor !== Array || data.length == 0) return ""; + + var outputdata = []; + data.forEach(function(e) { + var entry = { + "dateString": e.dateString + , "date": e.date + , "sgv": e.sgv + , "direction": e.direction + , "device": e.device + }; + outputdata.push(entry); + }); + + var fields = Object.keys(outputdata[0]); + var replacer = function(key, value) { + return value === null ? '' : value + } + var csv = outputdata.map(function(row) { + return fields.map(function(fieldName) { + return JSON.stringify(row[fieldName], replacer) + }).join(separator) + }); + return csv.join('\r\n'); } - /** - * @param model - * Prepare model based on explicit choice in route/path parameter. - */ - api.param('model', function(req, res, next, model) { - prepReqModel(req, model); - next(); + // console.log(JSON.stringify(req.headers)); + + // if no error, format the payload + // The general pattern here is to create an output stream that reformats + // the data correctly into the desired representation. + // The stream logic allows some streams to ensure that some basic rules, + // such as enforcing a type property to exist, are followed. + return res.format({ + 'text/plain': function() { + es.pipeline(output, force_typed_data(type_params), es.writeArray(function(err, out) { + var output = formatWithSeparator(out, "\t"); + res.send(output); + })); + } + , 'text/tab-separated-values': function() { + es.pipeline(output, force_typed_data(type_params), es.writeArray(function(err, out) { + var output = formatWithSeparator(out, '\t'); + res.send(output); + })); + } + , 'text/csv': function() { + es.pipeline(output, force_typed_data(type_params), es.writeArray(function(err, out) { + var output = formatWithSeparator(out, ','); + res.send(output); + })); + } + , 'application/json': function() { + // so long as every element has a `type` field, and some kind of + // date, we'll consider it valid + es.pipeline(output, force_typed_data(type_params), es.writeArray(function(err, out) { + res.json(out); + })); + } + , 'default': function() { + // Default to JSON output + // so long as every element has a `type` field, and some kind of + // date, we'll consider it valid + es.pipeline(output, force_typed_data(type_params), es.writeArray(function(err, out) { + res.json(out); + })); + } }); + } + + /** + * @method insert_entries + * middleware to process "uploads" of sgv data + * This inspects the http requests's incoming payload. This creates a + * validating stream for the appropriate type of payload, which is piped + * into the configured storage layer, saving the results in mongodb. + */ + // middleware to process "uploads" of sgv data + function insert_entries (req, res, next) { + // list of incoming records + var incoming = []; + // Potentially a single json encoded body. + // This can happen from either an url-encoded or json content-type. + if ('date' in req.body) { + // add it to the incoming list + incoming.push(req.body); + } + // potentially a list of json entries + if (req.body.length) { + // add them to the list + incoming = incoming.concat(req.body); + } /** - * @module get#/entries/current - * @route - * Get last entry. - * @response /definitions/Entries - */ - api.get('/entries/current', function(req, res, next) { - //assume sgv - req.params.model = 'sgv'; - entries.list({ - count: 1 - }, function(err, records) { - res.entries = records; - res.entries_err = err; - return next(); - }); - }, format_entries); - - /** - * @module get#/entries/:spec - * @route - * Fetch one entry by id - * @response /definitions/Entries - * @param String spec :spec is either the id of a record or model name to - * search. If it is an id, only the record with that id will be in the - * response. If the string is a model name, like `sgv`, `mbg`, et al, the - * usual query logic is performed biased towards that model type. - * Useful for filtering by type. - */ - api.get('/entries/:spec', function(req, res, next) { - if (isId(req.params.spec)) { - entries.getEntry(req.params.spec, function(err, entry) { - if (err) { - return next(err); - } - res.entries = [entry]; - res.entries_err = err; - req.query.find = req.query.find || {}; - if (entry) { - req.query.find.type = entry.type; - } else { - res.entries_err = 'No such id: \'' + req.params.spec + '\''; - } - next(); - }); - } else { - req.params.model = req.params.spec; - prepReqModel(req, req.params.model); - query_models(req, res, next); - } - }, format_entries); - - - /** - * @module get#/entries - * @route - * @response /definitions/Entries - * Use the `find` parameter to generate mongo queries. - * Default is `count=10`, for only 10 latest entries, reverse sorted by - * `find[date]`. - * - */ - api.get('/entries', ifModifiedSinceCTX, query_models, format_entries); - - /** - * @function echo_query - * Output the generated query object itself, instead of the query results. - * Useful for understanding how REST api parameters translate into mongodb - * queries. + * @function persist + * @returns {WritableStream} a writable persistent storage stream + * Sends stream elements into storage layer. + * Configures the storage layer stream. */ - function echo_query(req, res) { - var query = req.query; - // make a depth-wise copy of the original raw input - var input = JSON.parse(JSON.stringify(query)); - - // If "?count=" is present, use that number to decided how many to return. - if (!query.count) { - query.count = 10; - } - // bias towards entries, but allow expressing preference of storage layer - var storage = req.params.echo || 'entries'; - - // send payload with information about query itself - res.json({ - query: ctx[storage].query_for(query), - input: input, - params: req.params, - storage: storage - }); + function persist (fn) { + if (req.persist_entries) { + // store everything + return entries.persist(fn); + } + // support a preview mode, just lint everything + return es.pipeline(entries.map(), es.writeArray(fn)); } /** - * @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, assigning the payload to results on - * `res.entries`. + * @function done + * Final callback store results on `res.entries`, after all I/O is done. + * store results and move to the next middleware */ - function query_models(req, res, next) { - var query = req.query; - - // If "?count=" is present, use that number to decided how many to return. - if (!query.count) { - query.count = 10; - } - - // bias to entries, but allow expressing a preference - var storage = req.storage || ctx.entries; - // perform the query - storage.list(query, function payload(err, entries) { - // assign payload - res.entries = entries; - res.entries_err = err; - return next(); - }); - } - - function count_records(req, res, next) { - var query = req.query; - var storage = req.storage || ctx.entries; - storage.aggregate(query, function payload(err, entries) { - // assign payload - res.entries = entries; - res.entries_err = err; - return next(err); - }); + function done (err, result) { + // assign payload + res.entries = result; + res.entries_err = err; + return next(); } - function format_results(req, res, next) { - res.json(res.entries); - next(); + // pipe everything to persistent storage + // when finished, pass to the next piece of middleware + es.pipeline(es.readArray(incoming), persist(done)); + } + + /** + * @function prepReqModel + * @param {Request} req The request to inspect + * @param {String} model The name of the model to use if not found. + * Sets `req.query.find.type` to your chosen model. + */ + function prepReqModel (req, model) { + var type = model || 'sgv'; + if (!req.query.find) { + req.query.find = { + type: type + }; + } else { + req.query.find.type = type; } - - /** - * @function delete_records - * Delete entries. The query logic works the same way as find/list. This - * endpoint uses same search logic to remove records from the database. - */ - function delete_records(req, res, next) { - // bias towards model, but allow expressing a preference - if (!req.model) { - req.model = ctx.entries; - } - var query = req.query; - if (!query.count) { - query.count = 10 + } + + /** + * @param model + * Prepare model based on explicit choice in route/path parameter. + */ + api.param('model', function(req, res, next, model) { + prepReqModel(req, model); + next(); + }); + + /** + * @module get#/entries/current + * @route + * Get last entry. + * @response /definitions/Entries + */ + api.get('/entries/current', function(req, res, next) { + //assume sgv + req.params.model = 'sgv'; + entries.list({ + count: 1 + }, function(err, records) { + res.entries = records; + res.entries_err = err; + return next(); + }); + }, format_entries); + + /** + * @module get#/entries/:spec + * @route + * Fetch one entry by id + * @response /definitions/Entries + * @param String spec :spec is either the id of a record or model name to + * search. If it is an id, only the record with that id will be in the + * response. If the string is a model name, like `sgv`, `mbg`, et al, the + * usual query logic is performed biased towards that model type. + * Useful for filtering by type. + */ + api.get('/entries/:spec', function(req, res, next) { + if (isId(req.params.spec)) { + entries.getEntry(req.params.spec, function(err, entry) { + if (err) { + return next(err); } - // remove using the query - req.model.remove(query, function(err, stat) { - if (err) { - return next(err); - } - // yield some information about success of operation - res.json(stat); - return next(); - }); - } - - /** - * @param spec - * Middleware that prepares the :spec parameter in the routed path. - */ - api.param('spec', function(req, res, next, spec) { - if (isId(spec)) { - prepReqModel(req, req.params.model); - req.query = { - find: { - _id: req.params.spec - } - }; + res.entries = [entry]; + res.entries_err = err; + req.query.find = req.query.find || {}; + if (entry) { + req.query.find.type = entry.type; } else { - prepReqModel(req, req.params.model); + res.entries_err = 'No such id: \'' + req.params.spec + '\''; } next(); + }); + } else { + req.params.model = req.params.spec; + prepReqModel(req, req.params.model); + query_models(req, res, next); + } + }, format_entries); + + /** + * @module get#/entries + * @route + * @response /definitions/Entries + * Use the `find` parameter to generate mongo queries. + * Default is `count=10`, for only 10 latest entries, reverse sorted by + * `find[date]`. + * + */ + api.get('/entries', ifModifiedSinceCTX, query_models, format_entries); + + /** + * @function echo_query + * Output the generated query object itself, instead of the query results. + * Useful for understanding how REST api parameters translate into mongodb + * queries. + */ + function echo_query (req, res) { + var query = req.query; + // make a depth-wise copy of the original raw input + var input = JSON.parse(JSON.stringify(query)); + + // If "?count=" is present, use that number to decided how many to return. + if (!query.count) { + query.count = consts.ENTRIES_DEFAULT_COUNT; + } + // bias towards entries, but allow expressing preference of storage layer + var storage = req.params.echo || 'entries'; + + // send payload with information about query itself + res.json({ + query: ctx[storage].query_for(query) + , input: input + , params: req.params + , storage: storage }); + } + + /** + * @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, assigning the payload to results on + * `res.entries`. + */ + function query_models (req, res, next) { + var query = req.query; + + // If "?count=" is present, use that number to decided how many to return. + if (!query.count) { + query.count = consts.ENTRIES_DEFAULT_COUNT; + } - /** - * @param echo - * The echo parameter in the path routing parameters allows the echo - * endpoints to customize the storage layer. - */ - api.param('echo', function(req, res, next, echo) { - console.log('echo', echo); - if (!echo) { - req.params.echo = 'entries'; - } - next(); + // bias to entries, but allow expressing a preference + var storage = req.storage || ctx.entries; + // perform the query + storage.list(query, function payload (err, entries) { + // assign payload + res.entries = entries; + res.entries_err = err; + return next(); }); - - /** - * @module get#/echo/:echo/:model/:spec - * @routed - * Echo information about model/spec queries. - * Useful in understanding how REST API prepares queries against mongo. - */ - api.get('/echo/:echo/:model?/:spec?', echo_query); - - /** - * Prepare regexp patterns based on `prefix`, and `regex` parameters. - * Translates `/:prefix/:regex` strings into fancy mongo queries. - * @method prep_patterns - * @params String prefix - * @params String regex - * This performs bash style brace/glob pattern expansion in order to generate flexible series of regex patterns. - * Very useful in querying across days, but constrained hours of time. - * Consider the following examples: -``` -curl -s -g 'http://localhost:1337/api/v1/times/2015-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv -curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv -curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv - -``` - */ - function prep_patterns(req, res, next) { - // initialize empty pattern list. - var pattern = []; - // initialize a basic prefix - // also perform bash brace/glob-style expansion - var prefix = expand(req.params.prefix || '.*'); - - // if expansion leads to more than one prefix - if (prefix.length > 1) { - // pre-pend the prefix to the pattern list and wait to expand it as - // part of the full pattern - pattern.push('^' + req.params.prefix); - } - // append any regex parameters - if (req.params.regex) { - // prepend "match any" rule to their rule - pattern.push('.*' + req.params.regex); - } - // create a single pattern with all inputs considered - // expand the pattern using bash/glob style brace expansion to generate - // an array of patterns. - pattern = expand(pattern.join('')); - - /** - * Factory function to customize creation of RegExp patterns. - * @method iter_regex - * @param String prefix Default null - * @param String suffix Default null - * @returns function(pat) which turns the given pattern into a new - * RegExp with the prefix and suffix prepended, and appended, - * respectively. - */ - function iter_regex(prefix, suffix) { - /** - * @function make - * @returns RegExp Make a RegExp with configured prefix and suffix - */ - function make(pat) { - // concat the prefix, pattern, and suffix. - pat = (prefix ? prefix : '') + pat + (suffix ? suffix : ''); - // return RegExp. - return new RegExp(pat); - } - // return functor - return make; - } - - // save pattern for other middlewares, eg echo, query, etc. - req.pattern = pattern; - var matches = pattern.map(iter_regex()); - // prepare the query against a configurable field name. - var field = req.patternField; - var query = {}; - query[field] = { - // $regex: prefix, - // configure query to perform regex against list of potential regexp - $in: matches - }; - if (prefix.length === 1) { - // If there is a single prefix pattern, mongo can optimize this against - // an indexed field - query[field].$regex = prefix.map(iter_regex('^')).pop(); - } - - // Merge into existing query structure. - if (req.query.find) { - if (req.query.find[field]) { - req.query.find[field].$in = query[field].$in; - } else { - req.query.find[field] = query[field]; - } - } else { - req.query.find = query; - } - // Also assist in querying for the requested type. - if (req.params.type) { - req.query.find.type = req.params.type; + } + + function count_records (req, res, next) { + var query = req.query; + var storage = req.storage || ctx.entries; + storage.aggregate(query, function payload (err, entries) { + // assign payload + res.entries = entries; + res.entries_err = err; + return next(err); + }); + } + + function format_results (req, res, next) { + res.json(res.entries); + next(); + } + + /** + * @function delete_records + * Delete entries. The query logic works the same way as find/list. This + * endpoint uses same search logic to remove records from the database. + */ + function delete_records (req, res, next) { + // bias towards model, but allow expressing a preference + if (!req.model) { + req.model = ctx.entries; + } + var query = req.query; + if (!query.count) { + query.count = consts.ENTRIES_DEFAULT_COUNT; + } + // remove using the query + req.model.remove(query, function(err, stat) { + if (err) { + return next(err); + } + // yield some information about success of operation + res.json(stat); + return next(); + }); + } + + /** + * @param spec + * Middleware that prepares the :spec parameter in the routed path. + */ + api.param('spec', function(req, res, next, spec) { + if (isId(spec)) { + prepReqModel(req, req.params.model); + req.query = { + find: { + _id: req.params.spec } - next(); + }; + } else { + prepReqModel(req, req.params.model); } + next(); + }); + + /** + * @param echo + * The echo parameter in the path routing parameters allows the echo + * endpoints to customize the storage layer. + */ + api.param('echo', function(req, res, next, echo) { + console.log('echo', echo); + if (!echo) { + req.params.echo = 'entries'; + } + next(); + }); + + /** + * @module get#/echo/:echo/:model/:spec + * @routed + * Echo information about model/spec queries. + * Useful in understanding how REST API prepares queries against mongo. + */ + api.get('/echo/:echo/:model?/:spec?', echo_query); + + /** + * Prepare regexp patterns based on `prefix`, and `regex` parameters. + * Translates `/:prefix/:regex` strings into fancy mongo queries. + * @method prep_patterns + * @params String prefix + * @params String regex + * This performs bash style brace/glob pattern expansion in order to generate flexible series of regex patterns. + * Very useful in querying across days, but constrained hours of time. + * Consider the following examples: + ``` + curl -s -g 'http://localhost:1337/api/v1/times/2015-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv + curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv + curl -s -g 'http://localhost:1337/api/v1/times/20{14..15}/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120' | json -a dateString sgv + + ``` + */ + function prep_patterns (req, res, next) { + // initialize empty pattern list. + var pattern = []; + // initialize a basic prefix + // also perform bash brace/glob-style expansion + var prefix = expand(req.params.prefix || '.*'); + + // if expansion leads to more than one prefix + if (prefix.length > 1) { + // pre-pend the prefix to the pattern list and wait to expand it as + // part of the full pattern + pattern.push('^' + req.params.prefix); + } + // append any regex parameters + if (req.params.regex) { + // prepend "match any" rule to their rule + pattern.push('.*' + req.params.regex); + } + // create a single pattern with all inputs considered + // expand the pattern using bash/glob style brace expansion to generate + // an array of patterns. + + pattern = expand(pattern.join('')); + if (pattern.length == 0) pattern = ['']; /** - * @method prep_pattern_field - * Ensure that `req.patternField` is set to assist other middleware in - * deciding which field to generate queries against. - * Default is `dateString`, because that's the iso8601 field for sgv - * entries. + * Factory function to customize creation of RegExp patterns. + * @method iter_regex + * @param String prefix Default null + * @param String suffix Default null + * @returns function(pat) which turns the given pattern into a new + * RegExp with the prefix and suffix prepended, and appended, + * respectively. */ - function prep_pattern_field(req, res, next) { - // If req.params.field from routed path parameter is available use it. - if (req.params.field) { - req.patternField = req.params.field; - } else { - // Default is `dateString`. - req.patternField = 'dateString'; - } - next(); + function iter_regex (prefix, suffix) { + /** + * @function make + * @returns RegExp Make a RegExp with configured prefix and suffix + */ + function make (pat) { + // concat the prefix, pattern, and suffix. + pat = (prefix ? prefix : '') + pat + (suffix ? suffix : ''); + // return RegExp. + return new RegExp(pat); + } + // return functor + return make; } - /** - * @method prep_storage - * Prep storage layer for other middleware by setting `req.storage`. - * Some routed paths have a `storage` parameter available, when this is - * set, `req.storage will be set to that value. The default otherwise is - * the entries storage layer, because that's where sgv records are stored - * by default. - */ - function prep_storage(req, res, next) { - if (req.params.storage && _includes(['entries', 'treatments', 'devicestatus'], req.params.storage)) { - req.storage = ctx[req.params.storage]; - } else { - req.storage = ctx.entries; - } - next(); + // save pattern for other middlewares, eg echo, query, etc. + req.pattern = pattern; + var matches = pattern.map(iter_regex()); + // prepare the query against a configurable field name. + var field = req.patternField; + var query = {}; + query[field] = { + // $regex: prefix, + // configure query to perform regex against list of potential regexp + $in: matches + }; + if (prefix.length === 1) { + // If there is a single prefix pattern, mongo can optimize this against + // an indexed field + query[field].$regex = prefix.map(iter_regex('^')).pop(); } - /** - * @module get#/times/echo/:prefix/:regex - * Echo interface for the regex pattern generator. - * @routed - * Useful for understanding how the `/:prefix/:regex` route generates - * mongodb queries. - */ - api.get('/times/echo/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, function(req, res) { - res.json({ - req: { - params: req.params, - query: req.query - }, - pattern: req.pattern - }); + // Merge into existing query structure. + if (req.query.find) { + if (req.query.find[field]) { + req.query.find[field].$in = query[field].$in; + } else { + req.query.find[field] = query[field]; + } + } else { + req.query.find = query; + } + // Also assist in querying for the requested type. + if (req.params.type) { + req.query.find.type = req.params.type; + } + next(); + } + + /** + * @method prep_pattern_field + * Ensure that `req.patternField` is set to assist other middleware in + * deciding which field to generate queries against. + * Default is `dateString`, because that's the iso8601 field for sgv + * entries. + */ + function prep_pattern_field (req, res, next) { + // If req.params.field from routed path parameter is available use it. + if (req.params.field) { + req.patternField = req.params.field; + } else { + // Default is `dateString`. + req.patternField = 'dateString'; + } + next(); + } + + /** + * @method prep_storage + * Prep storage layer for other middleware by setting `req.storage`. + * Some routed paths have a `storage` parameter available, when this is + * set, `req.storage will be set to that value. The default otherwise is + * the entries storage layer, because that's where sgv records are stored + * by default. + */ + function prep_storage (req, res, next) { + if (req.params.storage && _includes(['entries', 'treatments', 'devicestatus'], req.params.storage)) { + req.storage = ctx[req.params.storage]; + } else { + req.storage = ctx.entries; + } + next(); + } + + /** + * @module get#/times/echo/:prefix/:regex + * Echo interface for the regex pattern generator. + * @routed + * Useful for understanding how the `/:prefix/:regex` route generates + * mongodb queries. + */ + api.get('/times/echo/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, function(req, res) { + res.json({ + req: { + params: req.params + , query: req.query + } + , pattern: req.pattern }); - - /** - * @module get#/times/:prefix/:regex - * Allows searching for modal times of day across days and months. -``` -/api/v1/times/2015-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 -/api/v1/times/20{14..15}-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 -/api/v1/times/20{14..15}/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 -``` - * @routed - * @response 200 /definitions/Entries - */ - api.get('/times/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, query_models, format_entries); - - api.get('/count/:storage/where', prep_storage, count_records, format_results); - + }); + + /** + * @module get#/times/:prefix/:regex + * Allows searching for modal times of day across days and months. + ``` + /api/v1/times/2015-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 + /api/v1/times/20{14..15}-04/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 + /api/v1/times/20{14..15}/T{13..18}:{00..15}'.json'?find[sgv][$gte]=120 + ``` + * @routed + * @response 200 /definitions/Entries + */ + api.get('/times/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, prep_patterns, query_models, format_entries); + + api.get('/count/:storage/where', prep_storage, count_records, format_results); + + /** + * @module get#/slice/:storage/:field/:type/:prefix/:regex + * @routed + * @response 200 /definitions/Entries + * Allows searching for modal times of day across days and months. + * Also allows specifying field to perform regexp on, the storage layer to + * use, as well as which type of model to look for. + ``` + /api/v1/slice/entries/dateString/mbg/2015.json + ``` + */ + api.get('/slice/:storage/:field/:type?/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, query_models, format_entries); + + /** + * @module post#/entries/preview + * Allow previewing your post content, just echos everything you + * posted back out. + * Similar to the echo api, useful to lint/debug upload problems. + */ + api.post('/entries/preview', ctx.authorization.isPermitted('api:entries:create'), function(req, res, next) { + // setting this flag tells insert_entries to not actually store the results + req.persist_entries = false; + next(); + }, insert_entries, format_entries); + + // Protect endpoints with authenticated api. + if (app.enabled('api')) { + // Create and store new sgv entries /** - * @module get#/slice/:storage/:field/:type/:prefix/:regex - * @routed - * @response 200 /definitions/Entries - * Allows searching for modal times of day across days and months. - * Also allows specifying field to perform regexp on, the storage layer to - * use, as well as which type of model to look for. -``` -/api/v1/slice/entries/dateString/mbg/2015.json -``` - */ - api.get('/slice/:storage/:field/:type?/:prefix?/:regex?', prep_storage, prep_pattern_field, prep_patterns, query_models, format_entries); - - /** - * @module post#/entries/preview - * Allow previewing your post content, just echos everything you - * posted back out. - * Similar to the echo api, useful to lint/debug upload problems. + * @module post#/entries + * Allow posting content to store. + * Stores incoming payload that follows basic rules about having a + * `type` field in `entries` storage layer. */ - api.post('/entries/preview', ctx.authorization.isPermitted('api:entries:create'), function(req, res, next) { - // setting this flag tells insert_entries to not actually store the results - req.persist_entries = false; - next(); + api.post('/entries/', ctx.authorization.isPermitted('api:entries:create'), function(req, res, next) { + // setting this flag tells insert_entries to store the results + req.persist_entries = true; + next(); }, insert_entries, format_entries); - // Protect endpoints with authenticated api. - if (app.enabled('api')) { - // Create and store new sgv entries - /** - * @module post#/entries - * Allow posting content to store. - * Stores incoming payload that follows basic rules about having a - * `type` field in `entries` storage layer. - */ - api.post('/entries/', ctx.authorization.isPermitted('api:entries:create'), function(req, res, next) { - // setting this flag tells insert_entries to store the results - req.persist_entries = true; - next(); - }, insert_entries, format_entries); - - /** - * @module delete#/entries/:spec - * @route - * Delete entries. The query logic works the same way as find/list. This - * endpoint uses same search logic to remove records from the database. - */ - api.delete('/entries/:spec', ctx.authorization.isPermitted('api:entries:delete'), function(req, res, next) { - // if ID, prepare to query for one record - if (isId(req.params.spec)) { - prepReqModel(req, req.params.model); - req.query = { - find: { - _id: req.params.spec - } - }; - } else { - req.params.model = req.params.spec; - prepReqModel(req, req.params.model); - if (req.query.find.type === '*') { - delete req.query.find.type; - } - } - next(); - }, delete_records); - + /** + * @module delete#/entries/:spec + * @route + * Delete entries. The query logic works the same way as find/list. This + * endpoint uses same search logic to remove records from the database. + */ + api.delete('/entries/:spec', ctx.authorization.isPermitted('api:entries:delete'), function(req, res, next) { + // if ID, prepare to query for one record + if (isId(req.params.spec)) { + prepReqModel(req, req.params.model); + req.query = { + find: { + _id: req.params.spec + } + }; + } else { + req.params.model = req.params.spec; + prepReqModel(req, req.params.model); + if (req.query.find.type === '*') { + delete req.query.find.type; + } + } + next(); + }, delete_records); - } + // delete record that match query + api.delete('/entries/', ctx.authorization.isPermitted('api:entries:delete'), delete_records); + } - return api; + return api; } // expose module -module.exports = configure; \ No newline at end of file +module.exports = configure; diff --git a/lib/api/googlehome/index.js b/lib/api/googlehome/index.js new file mode 100644 index 00000000000..2b2caa2a378 --- /dev/null +++ b/lib/api/googlehome/index.js @@ -0,0 +1,123 @@ +'use strict'; + +var moment = require('moment'); +var _each = require('lodash/each'); + +function configure (app, wares, ctx, env) { + var entries = ctx.entries; + var express = require('express') + , api = express.Router( ); + var translate = ctx.language.translate; + + // invoke common middleware + api.use(wares.sendJSONStatus); + // text body types get handled as raw buffer stream + api.use(wares.bodyParser.raw()); + // json body types get handled as parsed json + api.use(wares.bodyParser.json()); + + ctx.plugins.eachEnabledPlugin(function each(plugin){ + if (plugin.virtAsst) { + if (plugin.virtAsst.intentHandlers) { + console.log('Google Home: Plugin ' + plugin.name + ' supports Virtual Assistants'); + _each(plugin.virtAsst.intentHandlers, function (route) { + if (route) { + ctx.googleHome.configureIntentHandler(route.intent, route.intentHandler, route.metrics); + } + }); + } + if (plugin.virtAsst.rollupHandlers) { + console.log('Google Home: Plugin ' + plugin.name + ' supports rollups for Virtual Assistants'); + _each(plugin.virtAsst.rollupHandlers, function (route) { + console.log('Route'); + console.log(route); + if (route) { + ctx.googleHome.addToRollup(route.rollupGroup, route.rollupHandler, route.rollupName); + } + }); + } + } else { + console.log('Google Home: Plugin ' + plugin.name + ' does not support Virtual Assistants'); + } + }); + + api.post('/googlehome', ctx.authorization.isPermitted('api:*:read'), function (req, res, next) { + console.log('Incoming request from Google Home'); + var locale = req.body.queryResult.languageCode; + if(locale){ + if(locale.length > 2) { + locale = locale.substr(0, 2); + } + ctx.language.set(locale); + moment.locale(locale); + } + + var handler = ctx.googleHome.getIntentHandler(req.body.queryResult.intent.displayName, req.body.queryResult.parameters.metric); + if (handler){ + var sbx = initializeSandbox(); + handler(function (title, response) { + res.json(ctx.googleHome.buildSpeechletResponse(response, false)); + next( ); + }, req.body.queryResult.parameters, sbx); + } else { + res.json(ctx.googleHome.buildSpeechletResponse('I\'m sorry. I don\'t know what you\'re asking for. Could you say that again?', true)); + next( ); + } + }); + + ctx.googleHome.addToRollup('Status', function bgRollupHandler(slots, sbx, callback) { + entries.list({count: 1}, function (err, records) { + var direction; + if (translate(records[0].direction)) { + direction = translate(records[0].direction); + } else { + direction = records[0].direction; + } + var status = translate('virtAsstStatus', { + params: [ + sbx.scaleMgdl(records[0].sgv), + direction, + moment(records[0].date).from(moment(sbx.time)) + ] + }); + + callback(null, {results: status, priority: -1}); + }); + }, 'BG Status'); + + ctx.googleHome.configureIntentHandler('MetricNow', function (callback, slots, sbx, locale) { + entries.list({count: 1}, function(err, records) { + var direction; + if(translate(records[0].direction)){ + direction = translate(records[0].direction); + } else { + direction = records[0].direction; + } + var status = translate('virtAsstStatus', { + params: [ + sbx.scaleMgdl(records[0].sgv), + direction, + moment(records[0].date).from(moment(sbx.time))] + }); + + callback(translate('virtAsstTitleCurrentBG'), status); + }); + }, ['bg', 'blood glucose', 'number']); + + ctx.googleHome.configureIntentHandler('NSStatus', function (callback, slots, sbx, locale) { + ctx.googleHome.getRollup('Status', sbx, slots, locale, function (status) { + callback(translate('virtAsstTitleFullStatus'), status); + }); + }); + + function initializeSandbox() { + var sbx = require('../../sandbox')(); + sbx.serverInit(env, ctx); + ctx.plugins.setProperties(sbx); + return sbx; + } + + return api; +} + +module.exports = configure; \ No newline at end of file diff --git a/lib/api/index.js b/lib/api/index.js index 3c9748c93e6..4b3d6a4fcb6 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -40,7 +40,7 @@ function create (env, ctx) { app.use(wares.extensions([ 'json', 'svg', 'csv', 'txt', 'png', 'html', 'tsv' ])); - var entriesRouter = require('./entries/')(app, wares, ctx); + var entriesRouter = require('./entries/')(app, wares, ctx, env); // Entries and settings app.all('/entries*', entriesRouter); app.all('/echo/*', entriesRouter); @@ -48,13 +48,13 @@ function create (env, ctx) { app.all('/slice/*', entriesRouter); app.all('/count/*', entriesRouter); - app.all('/treatments*', require('./treatments/')(app, wares, ctx)); + app.all('/treatments*', require('./treatments/')(app, wares, ctx, env)); app.all('/profile*', require('./profile/')(app, wares, ctx)); - app.all('/devicestatus*', require('./devicestatus/')(app, wares, ctx)); + app.all('/devicestatus*', require('./devicestatus/')(app, wares, ctx, env)); app.all('/notifications*', require('./notifications-api')(app, wares, ctx)); app.all('/activity*', require('./activity/')(app, wares, ctx)); - + app.use('/', wares.sendJSONStatus, require('./verifyauth')(ctx)); app.all('/food*', require('./food/')(app, wares, ctx)); @@ -65,6 +65,10 @@ function create (env, ctx) { app.all('/alexa*', require('./alexa/')(app, wares, ctx, env)); } + if (ctx.googleHome) { + app.all('/googlehome*', require('./googlehome/')(app, wares, ctx, env)); + } + return app; } diff --git a/lib/api/notifications-api.js b/lib/api/notifications-api.js index f9810256ad8..d08a03a7ead 100644 --- a/lib/api/notifications-api.js +++ b/lib/api/notifications-api.js @@ -1,5 +1,7 @@ 'use strict'; +var consts = require('../constants'); + function configure (app, wares, ctx) { var express = require('express') , api = express.Router( ) @@ -7,9 +9,9 @@ function configure (app, wares, ctx) { api.post('/notifications/pushovercallback', function (req, res) { if (ctx.pushnotify.pushoverAck(req.body)) { - res.sendStatus(200); + res.sendStatus(consts.HTTP_OK); } else { - res.sendStatus(500); + res.sendStatus(consts.HTTP_INTERNAL_ERROR); } }); @@ -21,7 +23,7 @@ function configure (app, wares, ctx) { var time = req.query.time && Number(req.query.time); console.info('got api ack, level: ', level, ', time: ', time, ', query: ', req.query); ctx.notifications.ack(level, group, time, true); - res.sendStatus(200); + res.sendStatus(consts.HTTP_OK); }); } diff --git a/lib/api/notifications-v2.js b/lib/api/notifications-v2.js new file mode 100644 index 00000000000..16eac1de975 --- /dev/null +++ b/lib/api/notifications-v2.js @@ -0,0 +1,23 @@ +'use strict'; + +var consts = require('../constants'); + +function configure (app, ctx) { + var express = require('express') + , api = express.Router( ) + ; + + api.post('/loop', ctx.authorization.isPermitted('notifications:loop:push'), function (req, res) { + ctx.loop.sendNotification(req.body, req.connection.remoteAddress, function (error) { + if (error) { + res.status(consts.HTTP_INTERNAL_ERROR).send(error) + console.log("error sending notification to Loop: ", error); + } else { + res.sendStatus(consts.HTTP_OK); + } + }); + }); + + return api; +} +module.exports = configure; diff --git a/lib/api/profile/index.js b/lib/api/profile/index.js index 07c6e1dc65a..30f8fe240ca 100644 --- a/lib/api/profile/index.js +++ b/lib/api/profile/index.js @@ -44,7 +44,6 @@ function configure (app, wares, ctx) { res.json(created.ops); console.log('Profile created', created); } - }); }); diff --git a/lib/api/root.js b/lib/api/root.js new file mode 100644 index 00000000000..275660eeae8 --- /dev/null +++ b/lib/api/root.js @@ -0,0 +1,23 @@ +'use strict'; + +function configure () { + const express = require('express') + , api = express.Router( ) + , apiConst = require('./const') + , api3Const = require('../api3/const') + ; + + api.get('/versions', function getVersion (req, res) { + + const versions = [ + { version: apiConst.API1_VERSION, url: '/api/v1' }, + { version: apiConst.API2_VERSION, url: '/api/v2' }, + { version: api3Const.API3_VERSION, url: '/api/v3' } + ]; + + res.json(versions); + }); + + return api; +} +module.exports = configure; diff --git a/lib/api/status.js b/lib/api/status.js index 9d18b524ec3..a6ab2e82750 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -14,6 +14,9 @@ function configure (app, wares, env, ctx) { // Status badge/text/json api.get('/status', function (req, res) { + + var authToken = req.query.token || req.query.secret || ''; + var date = new Date(); var info = { status: 'ok' , name: app.get('name') @@ -25,7 +28,7 @@ function configure (app, wares, env, ctx) { , boluscalcEnabled: app.enabled('api') && env.settings.enable.indexOf('boluscalc') > -1 , settings: env.settings , extendedSettings: app.extendedClientSettings - , authorized: ctx.authorization.authorize(req.query.token || '') + , authorized: ctx.authorization.authorize(authToken) }; var badge = 'http://img.shields.io/badge/Nightscout-OK-green'; diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 3fff4ceb331..5d527fce6ac 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -7,126 +7,175 @@ var _isArray = require('lodash/isArray'); var consts = require('../../constants'); var moment = require('moment'); -function configure(app, wares, ctx) { - var express = require('express') - , api = express.Router(); - - api.use(wares.compression()); - api.use(wares.bodyParser({ - limit: 1048576 * 50 - })); - // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw({ - limit: 1048576 - })); - // json body types get handled as parsed json - api.use(wares.bodyParser.json({ - limit: 1048576 - })); - // also support url-encoded content-type - api.use(wares.bodyParser.urlencoded({ - limit: 1048576 - , extended: true - })); - // invoke common middleware - api.use(wares.sendJSONStatus); - - api.use(ctx.authorization.isPermitted('api:treatments:read')); - - // List treatments available - api.get('/treatments', function(req, res) { - var ifModifiedSince = req.get('If-Modified-Since'); - ctx.treatments.list(req.query, function(err, results) { - var d1 = null; - - _forEach(results, function clean(t) { - t.carbs = Number(t.carbs); - t.insulin = Number(t.insulin); - - var d2 = null; - - if (t.hasOwnProperty('created_at')) { - d2 = new Date(t.created_at); - } else { - if (t.hasOwnProperty('timestamp')) { - d2 = new Date(t.timestamp); - } - } - - if (d2 == null) { return; } - - if (d1 == null || d2.getTime() > d1.getTime()) { - d1 = d2; - } - }); - - if (!_isNil(d1)) res.setHeader('Last-Modified', d1.toUTCString()); - - if (ifModifiedSince && d1.getTime() <= moment(ifModifiedSince).valueOf()) { - res.status(304).send({ - status: 304 - , message: 'Not modified' - , type: 'internal' - }); - return; - } else { - return res.json(results); - } - }); - }); +function configure (app, wares, ctx, env) { + var express = require('express') + , api = express.Router(); + + api.use(wares.compression()); + api.use(wares.bodyParser({ + limit: 1048576 * 50 + })); + // text body types get handled as raw buffer stream + api.use(wares.bodyParser.raw({ + limit: 1048576 + })); + // json body types get handled as parsed json + api.use(wares.bodyParser.json({ + limit: 1048576 + })); + // also support url-encoded content-type + api.use(wares.bodyParser.urlencoded({ + limit: 1048576 + , extended: true + })); + // invoke common middleware + api.use(wares.sendJSONStatus); + + api.use(ctx.authorization.isPermitted('api:treatments:read')); + + // List treatments available + api.get('/treatments', function(req, res) { + var ifModifiedSince = req.get('If-Modified-Since'); + ctx.treatments.list(req.query, function(err, results) { + var d1 = null; + + const deNormalizeDates = env.settings.deNormalizeDates; + + _forEach(results, function clean (t) { + t.carbs = Number(t.carbs); + t.insulin = Number(t.insulin); + + // eslint-disable-next-line no-prototype-builtins + if (deNormalizeDates && t.hasOwnProperty('utcOffset')) { + const d = moment(t.created_at).utcOffset(t.utcOffset); + t.created_at = d.toISOString(true); + delete t.utcOffset; + } - function config_authed(app, api, wares, ctx) { + var d2 = null; - function post_response(req, res) { - var treatments = req.body; + if (t.hasOwnProperty('created_at')) { + d2 = new Date(t.created_at); + } else { + if (t.hasOwnProperty('timestamp')) { + d2 = new Date(t.timestamp); + } + } - if (!_isArray(treatments)) { - treatments = [treatments]; - }; + if (d2 == null) { return; } - ctx.treatments.create(treatments, function(err, created) { - if (err) { - console.log('Error adding treatment', err); - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - } else { - console.log('Treatment created'); - res.json(created); - } - }); + if (d1 == null || d2.getTime() > d1.getTime()) { + d1 = d2; } + }); - api.post('/treatments/', wares.bodyParser({ - limit: 1048576 * 50 - }), ctx.authorization.isPermitted('api:treatments:create'), post_response); + if (!_isNil(d1)) res.setHeader('Last-Modified', d1.toUTCString()); - api.delete('/treatments/:_id', ctx.authorization.isPermitted('api:treatments:delete'), function(req, res) { - ctx.treatments.remove(req.params._id, function() { - res.json({}); - }); + if (ifModifiedSince && d1.getTime() <= moment(ifModifiedSince).valueOf()) { + res.status(304).send({ + status: 304 + , message: 'Not modified' + , type: 'internal' }); + return; + } else { + return res.json(results); + } + }); + }); - // update record - api.put('/treatments/', ctx.authorization.isPermitted('api:treatments:update'), function(req, res) { - var data = req.body; - ctx.treatments.save(data, function(err, created) { - if (err) { - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - console.log('Error saving treatment'); - console.log(err); - } else { - res.json(created); - console.log('Treatment saved', data); - } - }); - }); + function config_authed (app, api, wares, ctx) { + + function post_response (req, res) { + var treatments = req.body; + + if (!_isArray(treatments)) { + treatments = [treatments]; + }; + + ctx.treatments.create(treatments, function(err, created) { + if (err) { + console.log('Error adding treatment', err); + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + } else { + console.log('Treatment created'); + res.json(created); + } + }); } - if (app.enabled('api') && app.enabled('careportal')) { - config_authed(app, api, wares, ctx); + api.post('/treatments/', wares.bodyParser({ + limit: 1048576 * 50 + }), ctx.authorization.isPermitted('api:treatments:create'), post_response); + + /** + * @function delete_records + * Delete treatments. The query logic works the same way as find/list. This + * endpoint uses same search logic to remove records from the database. + */ + function delete_records (req, res, next) { + var query = req.query; + if (!query.count) { + query.count = 10 + } + + console.log('Delete records with query: ', query); + + // remove using the query + ctx.treatments.remove(query, function(err, stat) { + if (err) { + console.log('treatments delete error: ', err); + return next(err); + } + // yield some information about success of operation + res.json(stat); + + console.log('treatments records deleted'); + + return next(); + }); } - return api; + api.delete('/treatments/:id', ctx.authorization.isPermitted('api:treatments:delete'), function(req, res, next) { + if (!req.query.find) { + req.query.find = { + _id: req.params.id + }; + } else { + req.query.find._id = req.params.id; + } + + if (req.query.find._id === '*') { + // match any record id + delete req.query.find._id; + } + next(); + }, delete_records); + + // delete record that match query + api.delete('/treatments/', ctx.authorization.isPermitted('api:treatments:delete'), delete_records); + + // update record + api.put('/treatments/', ctx.authorization.isPermitted('api:treatments:update'), function(req, res) { + var data = req.body; + ctx.treatments.save(data, function(err, created) { + if (err) { + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + console.log('Error saving treatment'); + console.log(err); + } else { + res.json(created); + console.log('Treatment saved', data); + } + }); + }); + } + + if (app.enabled('api') && app.enabled('careportal')) { + config_authed(app, api, wares, ctx); + } + + return api; } module.exports = configure; - diff --git a/lib/api/verifyauth.js b/lib/api/verifyauth.js index b3c67433d5b..a4eb2edf4ee 100644 --- a/lib/api/verifyauth.js +++ b/lib/api/verifyauth.js @@ -10,11 +10,15 @@ function configure (ctx) { ctx.authorization.resolveWithRequest(req, function resolved (err, result) { // this is used to see if req has api-secret equivalent authorization - var authorized = !err && + var authorized = !err && ctx.authorization.checkMultiple('*:*:create,update,delete', result.shiros) && //can write to everything ctx.authorization.checkMultiple('admin:*:*:*', result.shiros); //full admin permissions too + var response = { + message: authorized ? 'OK' : 'UNAUTHORIZED', + rolefound: result.subject ? 'FOUND' : 'NOTFOUND' + } - res.sendJSONStatus(res, consts.HTTP_OK, authorized ? 'OK' : 'UNAUTHORIZED'); + res.sendJSONStatus(res, consts.HTTP_OK, response); }); }); @@ -22,4 +26,3 @@ function configure (ctx) { } module.exports = configure; - diff --git a/lib/api3/const.json b/lib/api3/const.json new file mode 100644 index 00000000000..1c3dfd873ee --- /dev/null +++ b/lib/api3/const.json @@ -0,0 +1,53 @@ +{ + "API3_VERSION": "3.0.0-alpha", + "API3_SECURITY_ENABLE": true, + "API3_TIME_SKEW_TOLERANCE": 5, + "API3_DEDUP_FALLBACK_ENABLED": true, + "API3_CREATED_AT_FALLBACK_ENABLED": true, + "API3_MAX_LIMIT": 1000, + + "HTTP": { + "OK": 200, + "CREATED": 201, + "NO_CONTENT": 204, + "NOT_MODIFIED": 304, + "BAD_REQUEST": 400, + "UNAUTHORIZED": 401, + "FORBIDDEN": 403, + "NOT_FOUND": 404, + "GONE": 410, + "PRECONDITION_FAILED": 412, + "UNPROCESSABLE_ENTITY": 422, + "INTERNAL_ERROR": 500 + }, + + "MSG": { + "HTTP_400_BAD_LAST_MODIFIED": "Bad or missing Last-Modified header/parameter", + "HTTP_400_BAD_LIMIT": "Parameter limit out of tolerance", + "HTTP_400_BAD_REQUEST_BODY": "Bad or missing request body", + "HTTP_400_BAD_FIELD_IDENTIFIER": "Bad or missing identifier field", + "HTTP_400_BAD_FIELD_DATE": "Bad or missing date field", + "HTTP_400_BAD_FIELD_UTC": "Bad or missing utcOffset field", + "HTTP_400_BAD_FIELD_APP": "Bad or missing app field", + "HTTP_400_BAD_SKIP": "Parameter skip out of tolerance", + "HTTP_400_SORT_SORT_DESC": "Parameters sort and sort_desc cannot be combined", + "HTTP_400_UNSUPPORTED_FILTER_OPERATOR": "Unsupported filter operator {0}", + "HTTP_400_IMMUTABLE_FIELD": "Field {0} cannot be modified by the client", + "HTTP_401_BAD_DATE": "Bad Date header", + "HTTP_401_BAD_TOKEN": "Bad access token or JWT", + "HTTP_401_DATE_OUT_OF_TOLERANCE": "Date header out of tolerance", + "HTTP_401_MISSING_DATE": "Missing Date header", + "HTTP_401_MISSING_OR_BAD_TOKEN": "Missing or bad access token or JWT", + "HTTP_403_MISSING_PERMISSION": "Missing permission {0}", + "HTTP_403_NOT_USING_HTTPS": "Not using SSL/TLS", + "HTTP_422_READONLY_MODIFICATION": "Trying to modify read-only document", + "HTTP_500_INTERNAL_ERROR": "Internal Server Error", + "STORAGE_ERROR": "Database error", + "SOCKET_MISSING_OR_BAD_ACCESS_TOKEN": "Missing or bad accessToken", + "SOCKET_UNAUTHORIZED_TO_ANY": "Unauthorized to receive any collection" + }, + + "MIN_TIMESTAMP": 946684800000, + "MIN_UTC_OFFSET": -1440, + "MAX_UTC_OFFSET": 1440 +} \ No newline at end of file diff --git a/lib/api3/doc/security.md b/lib/api3/doc/security.md new file mode 100644 index 00000000000..0fdf4c7d2aa --- /dev/null +++ b/lib/api3/doc/security.md @@ -0,0 +1,48 @@ +# APIv3: Security + +### Enforcing HTTPS +APIv3 is ment to run only under SSL version of HTTP protocol, which provides: +- **message secrecy** - once the secure channel between client and server is closed the communication cannot be eavesdropped by any third party +- **message consistency** - each request/response is protected against modification by any third party (any forgery would be detected) +- **authenticity of identities** - once the client and server establish the secured channel, it is guaranteed that the identity of the client or server does not change during the whole session + +HTTPS (in use with APIv3) does not address the true identity of the client, but ensures the correct identity of the server. Furthermore, HTTPS does not prevent the resending of previously intercepted encrypted messages by an attacker. + + +--- +### Authentication and authorization +In APIv3, *API_SECRET* can no longer be used for authentication or authorization. Instead, a roles/permissions security model is used, which is managed in the *Admin tools* section of the web application. + + +The identity of the client is represented by the *subject* to whom the access level is set by assigning security *roles*. One or more *permissions* can be assigned to each role. Permissions are used in an [Apache Shiro-like style](http://shiro.apache.org/permissions.html "Apache Shiro-like style"). + + +For each security *subject*, the system automatically generates an *access token* that is difficult to guess since it is derived from the secret *API_SECRET*. The *access token* must be included in every secured API operation to decode the client's identity and determine its authorization level. In this way, it is then possible to resolve whether the client has the permission required by a particular API operation. + + +There are two ways to authorize API calls: +- use `token` query parameter to pass the *access token*, eg. `token=testreadab-76eaff2418bfb7e0` +- use so-called [JSON Web Tokens](https://jwt.io "JSON Web Tokens") + - at first let the `/api/v2/authorization/request` generates you a particular JWT, eg. `GET https://nsapiv3.herokuapp.com/api/v2/authorization/request/testreadab-76eaff2418bfb7e0` + - then, to each secure API operation attach a JWT token in the HTTP header, eg. `Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NUb2tlbiI6InRlc3RyZWFkYWItNzZlYWZmMjQxOGJmYjdlMCIsImlhdCI6MTU2NTAzOTczMSwiZXhwIjoxNTY1MDQzMzMxfQ.Y-OFtFJ-gZNJcnZfm9r4S7085Z7YKVPiaQxuMMnraVk` (until the JWT expires) + + + +--- +### Client timestamps +As previously mentioned, a potential attacker cannot decrypt the captured messages, but he can send them back to the client/server at any later time. APIv3 is partially preventing this by the temporal validity of each secured API call. + + +The client must include his current timestamp to each call so that the server can compare it against its clock. If the timestamp difference is not within the limit, the request is considered invalid. The tolerance limit is set in minutes in the `API3_TIME_SKEW_TOLERANCE` environment variable. + +There are two ways to include the client timestamp to the call: +- use `now` query parameter with UNIX epoch millisecond timestamp, eg. `now=1565041446908` +- add HTTP `Date` header to the request, eg. `Date: Sun, 12 May 2019 07:49:58 GMT` + + +The client can check each server response in the same way, because each response contains a server timestamp in the HTTP *Date* header as well. + + +--- +APIv3 security is enabled by default, but it can be completely disabled for development and debugging purposes by setting the web environment variable `API3_SECURITY_ENABLE=false`. +This setting is hazardous and it is strongly discouraged to be used for production purposes! diff --git a/lib/api3/doc/socket.md b/lib/api3/doc/socket.md new file mode 100644 index 00000000000..802a85e0235 --- /dev/null +++ b/lib/api3/doc/socket.md @@ -0,0 +1,142 @@ +# APIv3: Socket.IO storage modifications channel + +APIv3 has the ability to broadcast events about all created, edited and deleted documents, using Socket.IO library. + +This provides a real-time data exchange experience in combination with API REST operations. + +### Complete sample client code +```html + + + + + + + + APIv3 Socket.IO sample + + + + + + + + + + +``` + +**Important notice: Only changes made via APIv3 are being broadcasted. All direct database or APIv1 modifications are not included by this channel.** + +### Subscription (authorization) +The client must first subscribe to the channel that is exposed at `storage` namespace, ie the `/storage` subadress of the base Nightscout's web address (without `/api/v3` subaddress). +```javascript +const socket = io('https://nsapiv3.herokuapp.com/storage'); +``` + + +Subscription is requested by emitting `subscribe` event to the server, while including document with parameters: +* `accessToken`: required valid accessToken of the security subject, which has been prepared in *Admin Tools* of Nightscout. +* `collections`: optional array of collections which the client wants to subscribe to, by default all collections are requested) + +```javascript +socket.on('connect', function () { + socket.emit('subscribe', { + accessToken: 'testadmin-ad3b1f9d7b3f59d5', + collections: [ 'entries', 'treatments' ] + }, +``` + + +On the server, the subject is first identified and authenticated (by the accessToken) and then a verification takes place, if the subject has read access to each required collection. + +An exception is the `settings` collection for which `api:settings:admin` permission is required, for all other collections `api::read` permission is required. + + +If the authentication was successful and the client has read access to at least one collection, `success` = `true` is set in the response object and the field `collections` contains an array of collections which were actually subscribed (granted). +In other case `success` = `false` is set in the response object and the field `message` contains an error message. + +```javascript +function (data) { + if (data.success) { + console.log('subscribed for collections', data.collections); + } + else { + console.error(data.message); + } + }); +}); +``` + +### Receiving events +After the successful subscription the client can start listening to `create`, `update` and/or `delete` events of the socket. + + +##### create +`create` event fires each time a new document is inserted into the storage, regardless of whether it was CREATE or UPDATE operation of APIv3 (both of these operations are upserting/deduplicating, so they are "insert capable"). If the document already existed in the storage, the `update` event would be fired instead. + +The received object contains: +* `colName` field with the name of the affected collection +* the inserted document in `doc` field + +```javascript +socket.on('create', function (data) { + console.log(`${data.colName}:created document`, data.doc); +}); +``` + + +##### update +`update` event fires each time an existing document is modified in the storage, regardless of whether it was CREATE, UPDATE or PATCH operation of APIv3 (all of these operations are "update capable"). If the document did not yet exist in the storage, the `create` event would be fired instead. + +The received object contains: +* `colName` field with the name of the affected collection +* the new version of the modified document in `doc` field + +```javascript +socket.on('update', function (data) { + console.log(`${data.colName}:updated document`, data.doc); +}); +``` + + +##### delete +`delete` event fires each time an existing document is deleted in the storage, regardless of whether it was "soft" (marking as invalid) or permanent deleting. + +The received object contains: +* `colName` field with the name of the affected collection +* the identifier of the deleted document in the `identifier` field + +```javascript +socket.on('delete', function (data) { + console.log(`${data.colName}:deleted document with identifier`, data.identifier); +}); +``` \ No newline at end of file diff --git a/lib/api3/doc/tutorial.md b/lib/api3/doc/tutorial.md new file mode 100644 index 00000000000..3d8c656dfbd --- /dev/null +++ b/lib/api3/doc/tutorial.md @@ -0,0 +1,329 @@ +# APIv3: Basics tutorial + +Nightscout API v3 is a component of [cgm-remote-monitor](https://github.com/nightscout/cgm-remote-monitor) project. +It aims to provide lightweight, secured and HTTP REST compliant interface for your T1D treatment data exchange. + +There is a list of REST operations that the API v3 offers (inside `/api/v3` relative URL namespace), we will briefly introduce them in this file. + +Each NS instance with API v3 contains self-included OpenAPI specification at [/api/v3/swagger-ui-dist/](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/) relative URL. + + +--- +### VERSION + +[VERSION](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/#/other/get_version) operation gets you basic information about software packages versions. +It is public (there is no need to add authorization parameters/headers). + +Sample GET `/version` client code (to get actual versions): +```javascript +const request = require('request'); + +request('https://nsapiv3.herokuapp.com/api/v3/version', + (error, response, body) => console.log(body)); +``` +Sample result: +```javascript +{ + "version":"0.12.2", + "apiVersion":"3.0.0-alpha", + "srvDate":1564386001772, + "storage":{ + "storage":"mongodb", + "version":"3.6.12" + } +} +``` + + +--- +### STATUS + +[STATUS](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/#/other/get_status) operation gets you basic information about software packages versions. +It is public (there is no need to add authorization parameters/headers). + +Sample GET `/status` client code (to get my actual permissions): +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; + +request(`https://nsapiv3.herokuapp.com/api/v3/status?${auth}`, + (error, response, body) => console.log(body)); +``` +Sample result: +```javascript +{ + "version":"0.12.2", + "apiVersion":"3.0.0-alpha", + "srvDate":1564391740738, + "storage":{ + "storage":"mongodb", + "version":"3.6.12" + }, + "apiPermissions":{ + "devicestatus":"crud", + "entries":"crud", + "food":"crud", + "profile":"crud", + "settings":"crud", + "treatments":"crud" + } +} +``` +`"crud"` represents create + read + update + delete permissions for the collection. + + +--- +### SEARCH + +[SEARCH](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/SEARCH) operation filters, sorts, paginates and projects documents from the collection. + +Sample GET `/entries` client code (to retrieve last 3 BG values): +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; + +request(`https://nsapiv3.herokuapp.com/api/v3/entries?${auth}&sort$desc=date&limit=3&fields=dateString,sgv,direction`, + (error, response, body) => console.log(body)); +``` +Sample result: +``` +[ + { + "dateString":"2019-07-30T02:24:50.434+0200", + "sgv":115, + "direction":"FortyFiveDown" + }, + { + "dateString":"2019-07-30T02:19:50.374+0200", + "sgv":121, + "direction":"FortyFiveDown" + }, + { + "dateString":"2019-07-30T02:14:50.450+0200", + "sgv":129, + "direction":"FortyFiveDown" + } +] +``` + + +--- +### CREATE + +[CREATE](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/#/generic/post__collection_) operation inserts a new document into the collection. + +Sample POST `/treatments` client code: +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const doc = { + date: 1564591511232, // (new Date()).getTime(), + app: 'AndroidAPS', + device: 'Samsung XCover 4-861536030196001', + eventType: 'Correction Bolus', + insulin: 0.3 +}; +request({ + method: 'post', + body: doc, + json: true, + url: `https://nsapiv3.herokuapp.com/api/v3/treatments?${auth}` + }, + (error, response, body) => console.log(response.headers.location)); +``` +Sample result: +``` +/api/v3/treatments/95e1a6e3-1146-5d6a-a3f1-41567cae0895 +``` + + +--- +### READ + +[READ](https://nsapiv3.herokuapp.com/api/v3/swagger-ui-dist/#/generic/get__collection___identifier_) operation retrieves you a single document from the collection by its identifier. + +Sample GET `/treatments/{identifier}` client code: +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; + +request(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}`, + (error, response, body) => console.log(body)); +``` +Sample result: +``` +{ + "date":1564591511232, + "app":"AndroidAPS", + "device":"Samsung XCover 4-861536030196001", + "eventType":"Correction Bolus", + "insulin":0.3, + "identifier":"95e1a6e3-1146-5d6a-a3f1-41567cae0895", + "utcOffset":0, + "created_at":"2019-07-31T16:45:11.232Z", + "srvModified":1564591627732, + "srvCreated":1564591511711, + "subject":"test-admin" +} +``` + + +--- +### LAST MODIFIED + +[LAST MODIFIED](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/other/LAST-MODIFIED) operation finds the date of last modification for each collection. + +Sample GET `/lastModified` client code (to get latest modification dates): +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; + +request(`https://nsapiv3.herokuapp.com/api/v3/lastModified?${auth}`, + (error, response, body) => console.log(body)); +``` +Sample result: +```javascript +{ + "srvDate":1564591783202, + "collections":{ + "devicestatus":1564591490074, + "entries":1564591486801, + "profile":1548524042744, + "treatments":1564591627732 + } +} +``` + + +--- +### UPDATE + +[UPDATE](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/put__collection___identifier_) operation updates existing document in the collection. + +Sample PUT `/treatments/{identifier}` client code (to update `insulin` from 0.3 to 0.4): +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; +const doc = { + date: 1564591511232, + app: 'AndroidAPS', + device: 'Samsung XCover 4-861536030196001', + eventType: 'Correction Bolus', + insulin: 0.4 +}; + +request({ + method: 'put', + body: doc, + json: true, + url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` + }, + (error, response, body) => console.log(response.statusCode)); +``` +Sample result: +``` +204 +``` + + +--- +### PATCH + +[PATCH](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/patch__collection___identifier_) operation partially updates existing document in the collection. + +Sample PATCH `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5): +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; +const doc = { + insulin: 0.5 +}; + +request({ + method: 'patch', + body: doc, + json: true, + url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` + }, + (error, response, body) => console.log(response.statusCode)); +``` +Sample result: +``` +204 +``` + + +--- +### DELETE + +[DELETE](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/delete__collection___identifier_) operation deletes existing document from the collection. + +Sample DELETE `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5): +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; + +request({ + method: 'delete', + url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` + }, + (error, response, body) => console.log(response.statusCode)); +``` +Sample result: +``` +204 +``` + + +--- +### HISTORY + +[HISTORY](https://nsapiv3insecure.herokuapp.com/api/v3/swagger-ui-dist/index.html#/generic/HISTORY2) operation queries all changes since the timestamp. + +Sample HISTORY `/treatments/history/{lastModified}` client code: +```javascript +const request = require('request'); +const auth = `token=testadmin-ad3b1f9d7b3f59d5&now=${new Date().getTime()}`; +const lastModified = 1564521267421; + +request(`https://nsapiv3.herokuapp.com/api/v3/treatments/history/${lastModified}?${auth}`, + (error, response, body) => console.log(response.body)); +``` +Sample result: +``` +[ + { + "date":1564521267421, + "app":"AndroidAPS", + "device":"Samsung XCover 4-861536030196001", + "eventType":"Correction Bolus", + "insulin":0.5, + "utcOffset":0, + "created_at":"2019-07-30T21:14:27.421Z", + "identifier":"95e1a6e3-1146-5d6a-a3f1-41567cae0895", + "srvModified":1564592440416, + "srvCreated":1564592334853, + "subject":"test-admin", + "modifiedBy":"test-admin", + "isValid":false + }, + { + "date":1564592545299, + "app":"AndroidAPS", + "device":"Samsung XCover 4-861536030196001", + "eventType":"Snack Bolus", + "carbs":10, + "identifier":"267c43c2-f629-5191-a542-4f410c69e486", + "utcOffset":0, + "created_at":"2019-07-31T17:02:25.299Z", + "srvModified":1564592545781, + "srvCreated":1564592545781, + "subject":"test-admin" + } +] +``` +Notice the `"isValid":false` field marking the deletion of the document. \ No newline at end of file diff --git a/lib/api3/generic/collection.js b/lib/api3/generic/collection.js new file mode 100644 index 00000000000..0a1a29b3915 --- /dev/null +++ b/lib/api3/generic/collection.js @@ -0,0 +1,193 @@ +'use strict'; + +const apiConst = require('../const.json') + , _ = require('lodash') + , dateTools = require('../shared/dateTools') + , opTools = require('../shared/operationTools') + , stringTools = require('../shared/stringTools') + , CollectionStorage = require('../storage/mongoCollection') + , searchOperation = require('./search/operation') + , createOperation = require('./create/operation') + , readOperation = require('./read/operation') + , updateOperation = require('./update/operation') + , patchOperation = require('./patch/operation') + , deleteOperation = require('./delete/operation') + , historyOperation = require('./history/operation') + ; + +/** + * Generic collection (abstraction over each collection specifics) + * @param {string} colName - name of the collection inside the storage system + * @param {function} fallbackGetDate - function that tries to create srvModified virtually from other fields of document + * @param {Array} dedupFallbackFields - fields that all need to be matched to identify document via fallback deduplication + * @param {function} fallbackHistoryFilter - function that creates storage filter for all newer records (than the timestamp from first function parameter) + */ +function Collection ({ ctx, env, app, colName, storageColName, fallbackGetDate, dedupFallbackFields, + fallbackDateField }) { + + const self = this; + + self.colName = colName; + self.fallbackGetDate = fallbackGetDate; + self.dedupFallbackFields = app.get('API3_DEDUP_FALLBACK_ENABLED') ? dedupFallbackFields : []; + self.autoPruneDays = app.setENVTruthy('API3_AUTOPRUNE_' + colName.toUpperCase()); + self.nextAutoPrune = new Date(); + self.storage = new CollectionStorage(ctx, env, storageColName); + self.fallbackDateField = fallbackDateField; + + self.mapRoutes = function mapRoutes () { + const prefix = '/' + colName + , prefixId = prefix + '/:identifier' + , prefixHistory = prefix + '/history' + ; + + + // GET /{collection} + app.get(prefix, searchOperation(ctx, env, app, self)); + + // POST /{collection} + app.post(prefix, createOperation(ctx, env, app, self)); + + // GET /{collection}/history + app.get(prefixHistory, historyOperation(ctx, env, app, self)); + + // GET /{collection}/history + app.get(prefixHistory + '/:lastModified', historyOperation(ctx, env, app, self)); + + // GET /{collection}/{identifier} + app.get(prefixId, readOperation(ctx, env, app, self)); + + // PUT /{collection}/{identifier} + app.put(prefixId, updateOperation(ctx, env, app, self)); + + // PATCH /{collection}/{identifier} + app.patch(prefixId, patchOperation(ctx, env, app, self)); + + // DELETE /{collection}/{identifier} + app.delete(prefixId, deleteOperation(ctx, env, app, self)); + }; + + + /** + * Parse limit (max document count) from query string + */ + self.parseLimit = function parseLimit (req, res) { + const maxLimit = app.get('API3_MAX_LIMIT'); + let limit = maxLimit; + + if (req.query.limit) { + if (!isNaN(req.query.limit) && req.query.limit > 0 && req.query.limit <= maxLimit) { + limit = parseInt(req.query.limit); + } + else { + opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_LIMIT); + return null; + } + } + + return limit; + }; + + + + /** + * Fetch modified date from document (with possible fallback and back-fill to srvModified/srvCreated) + * @param {Object} doc - document loaded from database + */ + self.resolveDates = function resolveDates (doc) { + let modifiedDate; + try { + if (doc.srvModified) { + modifiedDate = new Date(doc.srvModified); + } + else { + if (typeof (self.fallbackGetDate) === 'function') { + modifiedDate = self.fallbackGetDate(doc); + if (modifiedDate) { + doc.srvModified = modifiedDate.getTime(); + } + } + } + + if (doc.srvModified && !doc.srvCreated) { + doc.srvCreated = modifiedDate.getTime(); + } + } + catch (error) { + console.warn(error); + } + return modifiedDate; + }; + + + /** + * Deletes old documents from the collection if enabled (for this collection) + * in the background (asynchronously) + * */ + self.autoPrune = function autoPrune () { + + if (!stringTools.isNumberInString(self.autoPruneDays)) + return; + + const autoPruneDays = parseFloat(self.autoPruneDays); + if (autoPruneDays <= 0) + return; + + if (new Date() > self.nextAutoPrune) { + + const deleteBefore = new Date(new Date().getTime() - (autoPruneDays * 24 * 3600 * 1000)); + + const filter = [ + { field: 'srvCreated', operator: 'lt', value: deleteBefore.getTime() }, + { field: 'created_at', operator: 'lt', value: deleteBefore.toISOString() }, + { field: 'date', operator: 'lt', value: deleteBefore.getTime() } + ]; + + // let's autoprune asynchronously (we won't wait for the result) + self.storage.deleteManyOr(filter, function deleteDone (err, result) { + if (err || !result) { + console.error(err); + } + + if (result.deleted) { + console.info('Auto-pruned ' + result.deleted + ' documents from ' + self.colName + ' collection '); + } + }); + + self.nextAutoPrune = new Date(new Date().getTime() + (3600 * 1000)); + } + }; + + + /** + * Parse date and utcOffset + optional created_at fallback + * @param {Object} doc + */ + self.parseDate = function parseDate (doc) { + if (!_.isEmpty(doc)) { + + let values = app.get('API3_CREATED_AT_FALLBACK_ENABLED') + ? [doc.date, doc.created_at] + : [doc.date]; + + let m = dateTools.parseToMoment(values); + if (m && m.isValid()) { + doc.date = m.valueOf(); + + if (typeof doc.utcOffset === 'undefined') { + doc.utcOffset = m.utcOffset(); + } + + if (app.get('API3_CREATED_AT_FALLBACK_ENABLED')) { + doc.created_at = m.toISOString(); + } + else { + if (doc.created_at) + delete doc.created_at; + } + } + } + } +} + +module.exports = Collection; \ No newline at end of file diff --git a/lib/api3/generic/create/insert.js b/lib/api3/generic/create/insert.js new file mode 100644 index 00000000000..4ac80a37e94 --- /dev/null +++ b/lib/api3/generic/create/insert.js @@ -0,0 +1,45 @@ +'use strict'; + +const apiConst = require('../../const.json') + , security = require('../../security') + , validate = require('./validate.js') + ; + +/** + * Insert new document into the collection + * @param {Object} opCtx + * @param {Object} doc + */ +async function insert (opCtx, doc) { + + const { ctx, auth, col, req, res } = opCtx; + + await security.demandPermission(opCtx, `api:${col.colName}:create`); + + if (validate(opCtx, doc) !== true) + return; + + const now = new Date; + doc.srvModified = now.getTime(); + doc.srvCreated = doc.srvModified; + + if (auth && auth.subject && auth.subject.name) { + doc.subject = auth.subject.name; + } + + const identifier = await col.storage.insertOne(doc); + + if (!identifier) + throw new Error('empty identifier'); + + res.setHeader('Last-Modified', now.toUTCString()); + res.setHeader('Location', `${req.baseUrl}${req.path}/${identifier}`); + res.status(apiConst.HTTP.CREATED).send({ }); + + ctx.bus.emit('storage-socket-create', { colName: col.colName, doc }); + col.autoPrune(); + ctx.bus.emit('data-received'); +} + + +module.exports = insert; \ No newline at end of file diff --git a/lib/api3/generic/create/operation.js b/lib/api3/generic/create/operation.js new file mode 100644 index 00000000000..39986a87ebd --- /dev/null +++ b/lib/api3/generic/create/operation.js @@ -0,0 +1,63 @@ +'use strict'; + +const _ = require('lodash') + , apiConst = require('../../const.json') + , security = require('../../security') + , insert = require('./insert') + , replace = require('../update/replace') + , opTools = require('../../shared/operationTools') + ; + + +/** + * CREATE: Inserts a new document into the collection + */ +async function create (opCtx) { + + const { col, req, res } = opCtx; + const doc = req.body; + + if (_.isEmpty(doc)) { + return opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_REQUEST_BODY); + } + + col.parseDate(doc); + opTools.resolveIdentifier(doc); + const identifyingFilter = col.storage.identifyingFilter(doc.identifier, doc, col.dedupFallbackFields); + + const result = await col.storage.findOneFilter(identifyingFilter, { }); + + if (!result) + throw new Error('empty result'); + + if (result.length > 0) { + const storageDoc = result[0]; + await replace(opCtx, doc, storageDoc, { isDeduplication: true }); + } + else { + await insert(opCtx, doc); + } +} + + +function createOperation (ctx, env, app, col) { + + return async function operation (req, res) { + + const opCtx = { app, ctx, env, col, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + await create(opCtx); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }; +} + +module.exports = createOperation; \ No newline at end of file diff --git a/lib/api3/generic/create/validate.js b/lib/api3/generic/create/validate.js new file mode 100644 index 00000000000..e978a3955e5 --- /dev/null +++ b/lib/api3/generic/create/validate.js @@ -0,0 +1,26 @@ +'use strict'; + +const apiConst = require('../../const.json') + , stringTools = require('../../shared/stringTools') + , opTools = require('../../shared/operationTools') + ; + + +/** + * Validation of document to create + * @param {Object} opCtx + * @param {Object} doc + * @returns string with error message if validation fails, true in case of success + */ +function validate (opCtx, doc) { + + const { res } = opCtx; + + if (typeof(doc.identifier) !== 'string' || stringTools.isNullOrWhitespace(doc.identifier)) { + return opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_FIELD_IDENTIFIER); + } + + return opTools.validateCommon(doc, res); +} + +module.exports = validate; \ No newline at end of file diff --git a/lib/api3/generic/delete/operation.js b/lib/api3/generic/delete/operation.js new file mode 100644 index 00000000000..8f9463f3ef9 --- /dev/null +++ b/lib/api3/generic/delete/operation.js @@ -0,0 +1,122 @@ +'use strict'; + +const apiConst = require('../../const.json') + , security = require('../../security') + , opTools = require('../../shared/operationTools') + ; + +/** + * DELETE: Deletes a document from the collection + */ +async function doDelete (opCtx) { + + const { col, req } = opCtx; + + await security.demandPermission(opCtx, `api:${col.colName}:delete`); + + if (await validateDelete(opCtx) !== true) + return; + + if (req.query.permanent && req.query.permanent === "true") { + await deletePermanently(opCtx); + } else { + await markAsDeleted(opCtx); + } +} + + +async function validateDelete (opCtx) { + + const { col, req, res } = opCtx; + + const identifier = req.params.identifier; + const result = await col.storage.findOne(identifier); + + if (!result) + throw new Error('empty result'); + + if (result.length === 0) { + return res.status(apiConst.HTTP.NOT_FOUND).end(); + } + else { + const storageDoc = result[0]; + + if (storageDoc.isReadOnly === true || storageDoc.readOnly === true || storageDoc.readonly === true) { + return opTools.sendJSONStatus(res, apiConst.HTTP.UNPROCESSABLE_ENTITY, + apiConst.MSG.HTTP_422_READONLY_MODIFICATION); + } + } + + return true; +} + + +async function deletePermanently (opCtx) { + + const { ctx, col, req, res } = opCtx; + + const identifier = req.params.identifier; + const result = await col.storage.deleteOne(identifier); + + if (!result) + throw new Error('empty result'); + + if (!result.deleted) { + return res.status(apiConst.HTTP.NOT_FOUND).end(); + } + + col.autoPrune(); + ctx.bus.emit('storage-socket-delete', { colName: col.colName, identifier }); + ctx.bus.emit('data-received'); + return res.status(apiConst.HTTP.NO_CONTENT).end(); +} + + +async function markAsDeleted (opCtx) { + + const { ctx, col, req, res, auth } = opCtx; + + const identifier = req.params.identifier; + const setFields = { 'isValid': false, 'srvModified': (new Date).getTime() }; + + if (auth && auth.subject && auth.subject.name) { + setFields.modifiedBy = auth.subject.name; + } + + const result = await col.storage.updateOne(identifier, setFields); + + if (!result) + throw new Error('empty result'); + + if (!result.updated) { + return res.status(apiConst.HTTP.NOT_FOUND).end(); + } + + ctx.bus.emit('storage-socket-delete', { colName: col.colName, identifier }); + col.autoPrune(); + ctx.bus.emit('data-received'); + return res.status(apiConst.HTTP.NO_CONTENT).end(); +} + + +function deleteOperation (ctx, env, app, col) { + + return async function operation (req, res) { + + const opCtx = { app, ctx, env, col, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + await doDelete(opCtx); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }; +} + +module.exports = deleteOperation; \ No newline at end of file diff --git a/lib/api3/generic/history/operation.js b/lib/api3/generic/history/operation.js new file mode 100644 index 00000000000..0929a09cc4f --- /dev/null +++ b/lib/api3/generic/history/operation.js @@ -0,0 +1,151 @@ +'use strict'; + +const dateTools = require('../../shared/dateTools') + , apiConst = require('../../const.json') + , security = require('../../security') + , opTools = require('../../shared/operationTools') + , FieldsProjector = require('../../shared/fieldsProjector') + , _ = require('lodash') + ; + +/** + * HISTORY: Retrieves incremental changes since timestamp + */ +async function history (opCtx, fieldsProjector) { + + const { req, res, col } = opCtx; + + let filter = parseFilter(opCtx) + , limit = col.parseLimit(req, res) + , projection = fieldsProjector.storageProjection() + , sort = prepareSort() + , skip = 0 + , onlyValid = false + , logicalOperator = 'or' + ; + + if (filter !== null && limit !== null && projection !== null) { + + const result = await col.storage.findMany(filter + , sort + , limit + , skip + , projection + , onlyValid + , logicalOperator); + + if (!result) + throw new Error('empty result'); + + if (result.length === 0) { + return res.status(apiConst.HTTP.NO_CONTENT).end(); + } + + _.each(result, col.resolveDates); + + const srvModifiedValues = _.map(result, function mapSrvModified (item) { + return item.srvModified; + }) + , maxSrvModified = _.max(srvModifiedValues); + + res.setHeader('Last-Modified', (new Date(maxSrvModified)).toUTCString()); + res.setHeader('ETag', 'W/"' + maxSrvModified + '"'); + + _.each(result, fieldsProjector.applyProjection); + + res.status(apiConst.HTTP.OK).send(result); + } +} + + +/** + * Parse history filtering criteria from Last-Modified header + */ +function parseFilter (opCtx) { + + const { req, res } = opCtx; + + let lastModified = null + , lastModifiedParam = req.params.lastModified + , operator = null; + + if (lastModifiedParam) { + + // using param in URL as a source of timestamp + const m = dateTools.parseToMoment(lastModifiedParam); + + if (m === null || !m.isValid()) { + opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_LAST_MODIFIED); + return null; + } + + lastModified = m.toDate(); + operator = 'gt'; + } + else { + // using request HTTP header as a source of timestamp + const lastModifiedHeader = req.get('Last-Modified'); + if (!lastModifiedHeader) { + opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_LAST_MODIFIED); + return null; + } + + try { + lastModified = dateTools.floorSeconds(new Date(lastModifiedHeader)); + } catch (err) { + opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_LAST_MODIFIED); + return null; + } + operator = 'gte'; + } + + return [ + { field: 'srvModified', operator: operator, value: lastModified.getTime() }, + { field: 'created_at', operator: operator, value: lastModified.toISOString() }, + { field: 'date', operator: operator, value: lastModified.getTime() } + ]; +} + + + +/** + * Prepare sorting for storage query + */ +function prepareSort () { + return { + srvModified: 1, + created_at: 1, + date: 1 + }; +} + + +function historyOperation (ctx, env, app, col) { + + return async function operation (req, res) { + + const opCtx = { app, ctx, env, col, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + if (col.colName === 'settings') { + await security.demandPermission(opCtx, `api:${col.colName}:admin`); + } else { + await security.demandPermission(opCtx, `api:${col.colName}:read`); + } + + const fieldsProjector = new FieldsProjector(req.query.fields); + + await history(opCtx, fieldsProjector); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }; +} + +module.exports = historyOperation; \ No newline at end of file diff --git a/lib/api3/generic/patch/operation.js b/lib/api3/generic/patch/operation.js new file mode 100644 index 00000000000..d7bb5fc2b4d --- /dev/null +++ b/lib/api3/generic/patch/operation.js @@ -0,0 +1,118 @@ +'use strict'; + +const _ = require('lodash') + , apiConst = require('../../const.json') + , security = require('../../security') + , validate = require('./validate.js') + , opTools = require('../../shared/operationTools') + , dateTools = require('../../shared/dateTools') + , FieldsProjector = require('../../shared/fieldsProjector') + ; + +/** + * PATCH: Partially updates document in the collection + */ +async function patch (opCtx) { + + const { req, res, col } = opCtx; + const doc = req.body; + + if (_.isEmpty(doc)) { + return opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_REQUEST_BODY); + } + + await security.demandPermission(opCtx, `api:${col.colName}:update`); + + col.parseDate(doc); + const identifier = req.params.identifier + , identifyingFilter = col.storage.identifyingFilter(identifier); + + const result = await col.storage.findOneFilter(identifyingFilter, { }); + + if (!result) + throw new Error('result empty'); + + if (result.length > 0) { + + const storageDoc = result[0]; + if (storageDoc.isValid === false) { + return res.status(apiConst.HTTP.GONE).end(); + } + + const modifiedDate = col.resolveDates(storageDoc) + , ifUnmodifiedSince = req.get('If-Unmodified-Since'); + + if (ifUnmodifiedSince + && dateTools.floorSeconds(modifiedDate) > dateTools.floorSeconds(new Date(ifUnmodifiedSince))) { + return res.status(apiConst.HTTP.PRECONDITION_FAILED).end(); + } + + await applyPatch(opCtx, identifier, doc, storageDoc); + } + else { + return res.status(apiConst.HTTP.NOT_FOUND).end(); + } +} + + +/** + * Patch existing document in the collection + * @param {Object} opCtx + * @param {string} identifier + * @param {Object} doc - fields and values to patch + * @param {Object} storageDoc - original (database) version of document + */ +async function applyPatch (opCtx, identifier, doc, storageDoc) { + + const { ctx, res, col, auth } = opCtx; + + if (validate(opCtx, doc, storageDoc) !== true) + return; + + const now = new Date; + doc.srvModified = now.getTime(); + + if (auth && auth.subject && auth.subject.name) { + doc.modifiedBy = auth.subject.name; + } + + const matchedCount = await col.storage.updateOne(identifier, doc); + + if (!matchedCount) + throw new Error('matchedCount empty'); + + res.setHeader('Last-Modified', now.toUTCString()); + res.status(apiConst.HTTP.NO_CONTENT).send({ }); + + const fieldsProjector = new FieldsProjector('_all'); + const patchedDocs = await col.storage.findOne(identifier, fieldsProjector); + const patchedDoc = patchedDocs[0]; + fieldsProjector.applyProjection(patchedDoc); + ctx.bus.emit('storage-socket-update', { colName: col.colName, doc: patchedDoc }); + + col.autoPrune(); + ctx.bus.emit('data-received'); +} + + +function patchOperation (ctx, env, app, col) { + + return async function operation (req, res) { + + const opCtx = { app, ctx, env, col, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + await patch(opCtx); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }; +} + +module.exports = patchOperation; \ No newline at end of file diff --git a/lib/api3/generic/patch/validate.js b/lib/api3/generic/patch/validate.js new file mode 100644 index 00000000000..057bb5c39e8 --- /dev/null +++ b/lib/api3/generic/patch/validate.js @@ -0,0 +1,19 @@ +'use strict'; + +const updateValidate = require('../update/validate') + ; + + +/** + * Validate document to patch + * @param {Object} opCtx + * @param {Object} doc + * @param {Object} storageDoc + * @returns string - null if validation fails + */ +function validate (opCtx, doc, storageDoc) { + + return updateValidate(opCtx, doc, storageDoc, { isPatching: true }); +} + +module.exports = validate; \ No newline at end of file diff --git a/lib/api3/generic/read/operation.js b/lib/api3/generic/read/operation.js new file mode 100644 index 00000000000..04d6f03bc70 --- /dev/null +++ b/lib/api3/generic/read/operation.js @@ -0,0 +1,75 @@ +'use strict'; + +const apiConst = require('../../const.json') + , security = require('../../security') + , opTools = require('../../shared/operationTools') + , dateTools = require('../../shared/dateTools') + , FieldsProjector = require('../../shared/fieldsProjector') + ; + +/** + * READ: Retrieves a single document from the collection + */ +async function read (opCtx) { + + const { req, res, col } = opCtx; + + await security.demandPermission(opCtx, `api:${col.colName}:read`); + + const fieldsProjector = new FieldsProjector(req.query.fields); + + const result = await col.storage.findOne(req.params.identifier + , fieldsProjector.storageProjection()); + + if (!result) + throw new Error('empty result'); + + if (result.length === 0) { + return res.status(apiConst.HTTP.NOT_FOUND).end(); + } + + const doc = result[0]; + if (doc.isValid === false) { + return res.status(apiConst.HTTP.GONE).end(); + } + + + const modifiedDate = col.resolveDates(doc); + if (modifiedDate) { + res.setHeader('Last-Modified', modifiedDate.toUTCString()); + + const ifModifiedSince = req.get('If-Modified-Since'); + + if (ifModifiedSince + && dateTools.floorSeconds(modifiedDate) <= dateTools.floorSeconds(new Date(ifModifiedSince))) { + return res.status(apiConst.HTTP.NOT_MODIFIED).end(); + } + } + + fieldsProjector.applyProjection(doc); + + res.status(apiConst.HTTP.OK).send(doc); +} + + +function readOperation (ctx, env, app, col) { + + return async function operation (req, res) { + + const opCtx = { app, ctx, env, col, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + await read(opCtx); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }; +} + +module.exports = readOperation; \ No newline at end of file diff --git a/lib/api3/generic/search/input.js b/lib/api3/generic/search/input.js new file mode 100644 index 00000000000..dbd37356760 --- /dev/null +++ b/lib/api3/generic/search/input.js @@ -0,0 +1,140 @@ +'use strict'; + +const apiConst = require('../../const.json') + , dateTools = require('../../shared/dateTools') + , stringTools = require('../../shared/stringTools') + , opTools = require('../../shared/operationTools') + ; + +const filterRegex = /(.*)\$([a-zA-Z]+)/; + + +/** + * Parse value of the parameter (to the correct data type) + */ +function parseValue(param, value) { + + value = stringTools.isNumberInString(value) ? parseFloat(value) : value; // convert number from string + + // convert boolean from string + if (value === 'true') + value = true; + + if (value === 'false') + value = false; + + // unwrap string in single quotes + if (typeof(value) === 'string' && value.startsWith('\'') && value.endsWith('\'')) { + value = value.substr(1, value.length - 2); + } + + if (['date', 'srvModified', 'srvCreated'].includes(param)) { + let m = dateTools.parseToMoment(value); + if (m && m.isValid()) { + value = m.valueOf(); + } + } + + if (param === 'created_at') { + let m = dateTools.parseToMoment(value); + if (m && m.isValid()) { + value = m.toISOString(); + } + } + + return value; +} + + +/** + * Parse filtering criteria from query string + */ +function parseFilter (req, res) { + const filter = [] + , reservedParams = ['token', 'sort', 'sort$desc', 'limit', 'skip', 'fields', 'now'] + , operators = ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 're'] + ; + + for (let param in req.query) { + if (!Object.prototype.hasOwnProperty.call(req.query, param) + || reservedParams.includes(param)) continue; + + let field = param + , operator = 'eq' + ; + + const match = filterRegex.exec(param); + if (match != null) { + operator = match[2]; + field = match[1]; + + if (!operators.includes(operator)) { + opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, + apiConst.MSG.HTTP_400_UNSUPPORTED_FILTER_OPERATOR.replace('{0}', operator)); + return null; + } + } + const value = parseValue(field, req.query[param]); + + filter.push({ field, operator, value }); + } + + return filter; +} + + +/** + * Parse sorting from query string + */ +function parseSort (req, res) { + let sort = {} + , sortDirection = 1; + + if (req.query.sort && req.query.sort$desc) { + opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_SORT_SORT_DESC); + return null; + } + + if (req.query.sort$desc) { + sortDirection = -1; + sort[req.query.sort$desc] = sortDirection; + } + else { + if (req.query.sort) { + sort[req.query.sort] = sortDirection; + } + } + + sort.identifier = sortDirection; + sort.created_at = sortDirection; + sort.date = sortDirection; + + return sort; +} + + +/** + * Parse skip (offset) from query string + */ +function parseSkip (req, res) { + let skip = 0; + + if (req.query.skip) { + if (!isNaN(req.query.skip) && req.query.skip >= 0) { + skip = parseInt(req.query.skip); + } + else { + opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_SKIP); + return null; + } + } + + return skip; +} + + +module.exports = { + parseFilter, + parseSort, + parseSkip +}; \ No newline at end of file diff --git a/lib/api3/generic/search/operation.js b/lib/api3/generic/search/operation.js new file mode 100644 index 00000000000..074f864d58a --- /dev/null +++ b/lib/api3/generic/search/operation.js @@ -0,0 +1,77 @@ +'use strict'; + +const apiConst = require('../../const.json') + , security = require('../../security') + , opTools = require('../../shared/operationTools') + , input = require('./input') + , _each = require('lodash/each') + , FieldsProjector = require('../../shared/fieldsProjector') + ; + + +/** + * SEARCH: Search documents from the collection + */ +async function search (opCtx) { + + const { req, res, col } = opCtx; + + if (col.colName === 'settings') { + await security.demandPermission(opCtx, `api:${col.colName}:admin`); + } else { + await security.demandPermission(opCtx, `api:${col.colName}:read`); + } + + const fieldsProjector = new FieldsProjector(req.query.fields); + + const filter = input.parseFilter(req, res) + , sort = input.parseSort(req, res) + , limit = col.parseLimit(req, res) + , skip = input.parseSkip(req, res) + , projection = fieldsProjector.storageProjection() + , onlyValid = true + ; + + + if (filter !== null && sort !== null && limit !== null && skip !== null && projection !== null) { + + const result = await col.storage.findMany(filter + , sort + , limit + , skip + , projection + , onlyValid); + + if (!result) + throw new Error('empty result'); + + _each(result, col.resolveDates); + + _each(result, fieldsProjector.applyProjection); + + res.status(apiConst.HTTP.OK).send(result); + } +} + + +function searchOperation (ctx, env, app, col) { + + return async function operation (req, res) { + + const opCtx = { app, ctx, env, col, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + await search(opCtx); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }; +} + +module.exports = searchOperation; \ No newline at end of file diff --git a/lib/api3/generic/setup.js b/lib/api3/generic/setup.js new file mode 100644 index 00000000000..17e118658dd --- /dev/null +++ b/lib/api3/generic/setup.js @@ -0,0 +1,103 @@ +'use strict'; + +const _ = require('lodash') + , dateTools = require('../shared/dateTools') + , Collection = require('./collection') + ; + + +function fallbackDate (doc) { + const m = dateTools.parseToMoment(doc.date); + return m == null || !m.isValid() + ? null + : m.toDate(); +} + + +function fallbackCreatedAt (doc) { + const m = dateTools.parseToMoment(doc.created_at); + return m == null || !m.isValid() + ? null + : m.toDate(); +} + + +function setupGenericCollections (ctx, env, app) { + const cols = { } + , enabledCols = app.get('enabledCollections'); + + if (_.includes(enabledCols, 'devicestatus')) { + cols.devicestatus = new Collection({ + ctx, env, app, + colName: 'devicestatus', + storageColName: env.devicestatus_collection || 'devicestatus', + fallbackGetDate: fallbackCreatedAt, + dedupFallbackFields: ['created_at', 'device'], + fallbackDateField: 'created_at' + }); + } + + const entriesCollection = new Collection({ + ctx, env, app, + colName: 'entries', + storageColName: env.entries_collection || 'entries', + fallbackGetDate: fallbackDate, + dedupFallbackFields: ['date', 'type'], + fallbackDateField: 'date' + }); + app.set('entriesCollection', entriesCollection); + + if (_.includes(enabledCols, 'entries')) { + cols.entries = entriesCollection; + } + + if (_.includes(enabledCols, 'food')) { + cols.food = new Collection({ + ctx, env, app, + colName: 'food', + storageColName: env.food_collection || 'food', + fallbackGetDate: fallbackCreatedAt, + dedupFallbackFields: ['created_at'], + fallbackDateField: 'created_at' + }); + } + + if (_.includes(enabledCols, 'profile')) { + cols.profile = new Collection({ + ctx, env, app, + colName: 'profile', + storageColName: env.profile_collection || 'profile', + fallbackGetDate: fallbackCreatedAt, + dedupFallbackFields: ['created_at'], + fallbackDateField: 'created_at' + }); + } + + if (_.includes(enabledCols, 'settings')) { + cols.settings = new Collection({ + ctx, env, app, + colName: 'settings', + storageColName: env.settings_collection || 'settings' + }); + } + + if (_.includes(enabledCols, 'treatments')) { + cols.treatments = new Collection({ + ctx, env, app, + colName: 'treatments', + storageColName: env.treatments_collection || 'treatments', + fallbackGetDate: fallbackCreatedAt, + dedupFallbackFields: ['created_at', 'eventType'], + fallbackDateField: 'created_at' + }); + } + + _.forOwn(cols, function forMember (col) { + col.mapRoutes(); + }); + + app.set('collections', cols); +} + + +module.exports = setupGenericCollections; diff --git a/lib/api3/generic/update/operation.js b/lib/api3/generic/update/operation.js new file mode 100644 index 00000000000..3e517a32d11 --- /dev/null +++ b/lib/api3/generic/update/operation.js @@ -0,0 +1,86 @@ +'use strict'; + +const _ = require('lodash') + , dateTools = require('../../shared/dateTools') + , apiConst = require('../../const.json') + , security = require('../../security') + , insert = require('../create/insert') + , replace = require('./replace') + , opTools = require('../../shared/operationTools') + ; + +/** + * UPDATE: Updates a document in the collection + */ +async function update (opCtx) { + + const { col, req, res } = opCtx; + const doc = req.body; + + if (_.isEmpty(doc)) { + return opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_REQUEST_BODY); + } + + col.parseDate(doc); + opTools.resolveIdentifier(doc); + + const identifier = req.params.identifier + , identifyingFilter = col.storage.identifyingFilter(identifier); + + const result = await col.storage.findOneFilter(identifyingFilter, { }); + + if (!result) + throw new Error('empty result'); + + doc.identifier = identifier; + + if (result.length > 0) { + await updateConditional(opCtx, doc, result[0]); + } + else { + await insert(opCtx, doc); + } +} + + +async function updateConditional (opCtx, doc, storageDoc) { + + const { col, req, res } = opCtx; + + if (storageDoc.isValid === false) { + return res.status(apiConst.HTTP.GONE).end(); + } + + const modifiedDate = col.resolveDates(storageDoc) + , ifUnmodifiedSince = req.get('If-Unmodified-Since'); + + if (ifUnmodifiedSince + && dateTools.floorSeconds(modifiedDate) > dateTools.floorSeconds(new Date(ifUnmodifiedSince))) { + return res.status(apiConst.HTTP.PRECONDITION_FAILED).end(); + } + + await replace(opCtx, doc, storageDoc); +} + + +function updateOperation (ctx, env, app, col) { + + return async function operation (req, res) { + + const opCtx = { app, ctx, env, col, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + await update(opCtx); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }; +} + +module.exports = updateOperation; \ No newline at end of file diff --git a/lib/api3/generic/update/replace.js b/lib/api3/generic/update/replace.js new file mode 100644 index 00000000000..ca490b31136 --- /dev/null +++ b/lib/api3/generic/update/replace.js @@ -0,0 +1,52 @@ +'use strict'; + +const apiConst = require('../../const.json') + , security = require('../../security') + , validate = require('./validate.js') + ; + +/** + * Replace existing document in the collection + * @param {Object} opCtx + * @param {any} doc - new version of document to set + * @param {any} storageDoc - old version of document (existing in the storage) + * @param {Object} options + */ +async function replace (opCtx, doc, storageDoc, options) { + + const { ctx, auth, col, req, res } = opCtx; + const { isDeduplication } = options || {}; + + await security.demandPermission(opCtx, `api:${col.colName}:update`); + + if (validate(opCtx, doc, storageDoc, { isDeduplication }) !== true) + return; + + const now = new Date; + doc.srvModified = now.getTime(); + doc.srvCreated = storageDoc.srvCreated || doc.srvModified; + + if (auth && auth.subject && auth.subject.name) { + doc.subject = auth.subject.name; + } + + const matchedCount = await col.storage.replaceOne(storageDoc.identifier, doc); + + if (!matchedCount) + throw new Error('empty matchedCount'); + + res.setHeader('Last-Modified', now.toUTCString()); + + if (storageDoc.identifier !== doc.identifier || isDeduplication) { + res.setHeader('Location', `${req.baseUrl}${req.path}/${doc.identifier}`); + } + + res.status(apiConst.HTTP.NO_CONTENT).send({ }); + + ctx.bus.emit('storage-socket-update', { colName: col.colName, doc }); + col.autoPrune(); + ctx.bus.emit('data-received'); +} + + +module.exports = replace; \ No newline at end of file diff --git a/lib/api3/generic/update/validate.js b/lib/api3/generic/update/validate.js new file mode 100644 index 00000000000..b36e1410067 --- /dev/null +++ b/lib/api3/generic/update/validate.js @@ -0,0 +1,48 @@ +'use strict'; + +const apiConst = require('../../const.json') + , opTools = require('../../shared/operationTools') + ; + + +/** + * Validation of document to update + * @param {Object} opCtx + * @param {Object} doc + * @param {Object} storageDoc + * @param {Object} options + * @returns string with error message if validation fails, true in case of success + */ +function validate (opCtx, doc, storageDoc, options) { + + const { res } = opCtx; + const { isPatching, isDeduplication } = options || {}; + + const immutable = ['identifier', 'date', 'utcOffset', 'eventType', 'device', 'app', + 'srvCreated', 'subject', 'srvModified', 'modifiedBy', 'isValid']; + + if (storageDoc.isReadOnly === true || storageDoc.readOnly === true || storageDoc.readonly === true) { + return opTools.sendJSONStatus(res, apiConst.HTTP.UNPROCESSABLE_ENTITY, + apiConst.MSG.HTTP_422_READONLY_MODIFICATION); + } + + for (const field of immutable) { + + // change of identifier is allowed in deduplication (for APIv1 documents) + if (field === 'identifier' && isDeduplication) + continue; + + // changing deleted document is without restrictions + if (storageDoc.isValid === false) + continue; + + if (typeof(doc[field]) !== 'undefined' && doc[field] !== storageDoc[field]) { + return opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, + apiConst.MSG.HTTP_400_IMMUTABLE_FIELD.replace('{0}', field)); + } + } + + return opTools.validateCommon(doc, res, { isPatching }); +} + +module.exports = validate; \ No newline at end of file diff --git a/lib/api3/index.js b/lib/api3/index.js new file mode 100644 index 00000000000..5b1799c78fd --- /dev/null +++ b/lib/api3/index.js @@ -0,0 +1,107 @@ +'use strict'; + +const express = require('express') + , bodyParser = require('body-parser') + , StorageSocket = require('./storageSocket') + , apiConst = require('./const.json') + , security = require('./security') + , genericSetup = require('./generic/setup') + , swaggerSetup = require('./swagger') + ; + +function configure (env, ctx) { + + const self = { } + , app = express() + ; + + self.setENVTruthy = function setENVTruthy (varName, defaultValue) { + //for some reason Azure uses this prefix, maybe there is a good reason + let value = process.env['CUSTOMCONNSTR_' + varName] + || process.env['CUSTOMCONNSTR_' + varName.toLowerCase()] + || process.env[varName] + || process.env[varName.toLowerCase()]; + + value = value != null ? value : defaultValue; + + if (typeof value === 'string' && (value.toLowerCase() === 'on' || value.toLowerCase() === 'true')) { value = true; } + if (typeof value === 'string' && (value.toLowerCase() === 'off' || value.toLowerCase() === 'false')) { value = false; } + + app.set(varName, value); + return value; + }; + app.setENVTruthy = self.setENVTruthy; + + + self.setupApiEnvironment = function setupApiEnvironment () { + + app.use(bodyParser.json({ + limit: 1048576 * 50 + }), function errorHandler (err, req, res, next) { + console.error(err); + res.status(apiConst.HTTP.INTERNAL_ERROR).json({ + status: apiConst.HTTP.INTERNAL_ERROR, + message: apiConst.MSG.HTTP_500_INTERNAL_ERROR + }); + if (next) { // we need 4th parameter next to behave like error handler, but we have to use it to prevent "unused variable" message + } + }); + + // we don't need these here + app.set('etag', false); + app.set('x-powered-by', false); // this seems to be unreliable + app.use(function (req, res, next) { + res.removeHeader('x-powered-by'); + next(); + }); + + app.set('name', env.name); + app.set('version', env.version); + app.set('apiVersion', apiConst.API3_VERSION); + app.set('units', env.DISPLAY_UNITS); + app.set('ci', process.env['CI'] ? true: false); + app.set('enabledCollections', ['devicestatus', 'entries', 'food', 'profile', 'settings', 'treatments']); + + self.setENVTruthy('API3_SECURITY_ENABLE', apiConst.API3_SECURITY_ENABLE); + self.setENVTruthy('API3_TIME_SKEW_TOLERANCE', apiConst.API3_TIME_SKEW_TOLERANCE); + self.setENVTruthy('API3_DEDUP_FALLBACK_ENABLED', apiConst.API3_DEDUP_FALLBACK_ENABLED); + self.setENVTruthy('API3_CREATED_AT_FALLBACK_ENABLED', apiConst.API3_CREATED_AT_FALLBACK_ENABLED); + self.setENVTruthy('API3_MAX_LIMIT', apiConst.API3_MAX_LIMIT); + }; + + + self.setupApiRoutes = function setupApiRoutes () { + + app.get('/version', require('./specific/version')(app, ctx, env)); + + if (app.get('env') === 'development' || app.get('ci')) { // for development and testing purposes only + app.get('/test', async function test (req, res) { + + try { + const opCtx = {app, ctx, env, req, res}; + opCtx.auth = await security.authenticate(opCtx); + await security.demandPermission(opCtx, 'api:entries:read'); + res.status(apiConst.HTTP.OK).end(); + } catch (error) { + console.error(error); + } + }); + } + + app.get('/lastModified', require('./specific/lastModified')(app, ctx, env)); + + app.get('/status', require('./specific/status')(app, ctx, env)); + }; + + + self.setupApiEnvironment(); + genericSetup(ctx, env, app); + self.setupApiRoutes(); + swaggerSetup(app); + + ctx.storageSocket = new StorageSocket(app, env, ctx); + + return app; +} + +module.exports = configure; diff --git a/lib/api3/security.js b/lib/api3/security.js new file mode 100644 index 00000000000..33099d88f12 --- /dev/null +++ b/lib/api3/security.js @@ -0,0 +1,122 @@ +'use strict'; + +const moment = require('moment') + , apiConst = require('./const.json') + , _ = require('lodash') + , shiroTrie = require('shiro-trie') + , dateTools = require('./shared/dateTools') + , opTools = require('./shared/operationTools') + ; + + +/** + * Check if Date header in HTTP request (or 'now' query parameter) is present and valid (with error response sending) + */ +function checkDateHeader (opCtx) { + + const { app, req, res } = opCtx; + + let dateString = req.header('Date'); + if (!dateString) { + dateString = req.query.now; + } + + if (!dateString) { + return opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_MISSING_DATE); + } + + let dateMoment = dateTools.parseToMoment(dateString); + if (!dateMoment) { + return opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_BAD_DATE); + } + + let nowMoment = moment(new Date()); + let diffMinutes = moment.duration(nowMoment.diff(dateMoment)).asMinutes(); + + if (Math.abs(diffMinutes) > app.get('API3_TIME_SKEW_TOLERANCE')) { + return opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_DATE_OUT_OF_TOLERANCE); + } + + return true; +} + + +function authenticate (opCtx) { + return new Promise(function promise (resolve, reject) { + + let { app, ctx, req, res } = opCtx; + + if (!app.get('API3_SECURITY_ENABLE')) { + const adminShiro = shiroTrie.new(); + adminShiro.add('*'); + return resolve({ shiros: [ adminShiro ] }); + } + + if (req.protocol !== 'https') { + return reject( + opTools.sendJSONStatus(res, apiConst.HTTP.FORBIDDEN, apiConst.MSG.HTTP_403_NOT_USING_HTTPS)); + } + + const checkDateResult = checkDateHeader(opCtx); + if (checkDateResult !== true) { + return checkDateResult; + } + + let token = ctx.authorization.extractToken(req); + if (!token) { + return reject( + opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_MISSING_OR_BAD_TOKEN)); + } + + ctx.authorization.resolve({ token }, function resolveFinish (err, result) { + if (err) { + return reject( + opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_BAD_TOKEN)); + } + else { + return resolve(result); + } + }); + }); +} + + +/** + * Checks for the permission from the authorization without error response sending + * @param {any} auth + * @param {any} permission + */ +function checkPermission (auth, permission) { + + if (auth) { + const found = _.find(auth.shiros, function checkEach (shiro) { + return shiro && shiro.check(permission); + }); + return _.isObject(found); + } + else { + return false; + } +} + + + +function demandPermission (opCtx, permission) { + return new Promise(function promise (resolve, reject) { + const { auth, res } = opCtx; + + if (checkPermission(auth, permission)) { + return resolve(true); + } else { + return reject( + opTools.sendJSONStatus(res, apiConst.HTTP.FORBIDDEN, apiConst.MSG.HTTP_403_MISSING_PERMISSION.replace('{0}', permission))); + } + }); +} + + +module.exports = { + authenticate, + checkPermission, + demandPermission +}; \ No newline at end of file diff --git a/lib/api3/shared/dateTools.js b/lib/api3/shared/dateTools.js new file mode 100644 index 00000000000..14b67f9e109 --- /dev/null +++ b/lib/api3/shared/dateTools.js @@ -0,0 +1,78 @@ +'use strict'; + +const moment = require('moment') + , stringTools = require('./stringTools') + , apiConst = require('../const.json') + ; + + +/** + * Floor date to whole seconds (cut off milliseconds) + * @param {Date} date + */ +function floorSeconds (date) { + let ms = date.getTime(); + ms -= ms % 1000; + return new Date(ms); +} + + +/** + * Parse date as moment object from value or array of values. + * @param {any} value + */ +function parseToMoment (value) +{ + if (!value) + return null; + + if (Array.isArray(value)) { + for (let item of value) { + let m = parseToMoment(item); + + if (m !== null) + return m; + } + } + else { + + if (typeof value === 'string' && stringTools.isNumberInString(value)) { + value = parseFloat(value); + } + + if (typeof value === 'number') { + let m = moment(value); + + if (!m.isValid()) + return null; + + if (m.valueOf() < apiConst.MIN_TIMESTAMP) + m = moment.unix(m); + + if (!m.isValid() || m.valueOf() < apiConst.MIN_TIMESTAMP) + return null; + + return m; + } + + if (typeof value === 'string') { + let m = moment.parseZone(value, moment.ISO_8601); + + if (!m.isValid()) + m = moment.parseZone(value, moment.RFC_2822); + + if (!m.isValid() || m.valueOf() < apiConst.MIN_TIMESTAMP) + return null; + + return m; + } + } + + // no parsing option succeeded => failure + return null; +} + +module.exports = { + floorSeconds, + parseToMoment +}; diff --git a/lib/api3/shared/fieldsProjector.js b/lib/api3/shared/fieldsProjector.js new file mode 100644 index 00000000000..921c7cc6df8 --- /dev/null +++ b/lib/api3/shared/fieldsProjector.js @@ -0,0 +1,82 @@ +'use strict'; + +const _each = require('lodash/each'); + +/** + * Decoder of 'fields' parameter providing storage projections + * @param {string} fieldsString - fields parameter from user + */ +function FieldsProjector (fieldsString) { + + const self = this + , exclude = []; + let specific = null; + + switch (fieldsString) + { + case '_all': + break; + + default: + if (fieldsString) { + specific = fieldsString.split(','); + } + } + + const systemFields = ['identifier', 'srvCreated', 'created_at', 'date']; + + /** + * Prepare projection definition for storage query + * */ + self.storageProjection = function storageProjection () { + const projection = { }; + + if (specific) { + _each(specific, function include (field) { + projection[field] = 1; + }); + + _each(systemFields, function include (field) { + projection[field] = 1; + }); + } + else { + _each(exclude, function exclude (field) { + projection[field] = 0; + }); + + _each(exclude, function exclude (field) { + if (systemFields.indexOf(field) >= 0) { + delete projection[field]; + } + }); + } + + return projection; + }; + + + /** + * Cut off unwanted fields from given document + * @param {Object} doc + */ + self.applyProjection = function applyProjection (doc) { + + if (specific) { + for(const field in doc) { + if (specific.indexOf(field) === -1) { + delete doc[field]; + } + } + } + else { + _each(exclude, function include (field) { + if (typeof(doc[field]) !== 'undefined') { + delete doc[field]; + } + }); + } + }; +} + +module.exports = FieldsProjector; \ No newline at end of file diff --git a/lib/api3/shared/operationTools.js b/lib/api3/shared/operationTools.js new file mode 100644 index 00000000000..1955b9c2068 --- /dev/null +++ b/lib/api3/shared/operationTools.js @@ -0,0 +1,111 @@ +'use strict'; + +const apiConst = require('../const.json') + , stringTools = require('./stringTools') + , uuidv5 = require('uuid/v5') + , uuidNamespace = [...Buffer.from("NightscoutRocks!", "ascii")] // official namespace for NS :-) + ; + +function sendJSONStatus (res, status, title, description, warning) { + + const json = { + status: status, + message: title, + description: description + }; + + // Add optional warning message. + if (warning) { json.warning = warning; } + + res.status(status).json(json); + + return title; +} + + +/** + * Validate document's common fields + * @param {Object} doc + * @param {any} res + * @param {Object} options + * @returns {any} - string with error message if validation fails, true in case of success + */ +function validateCommon (doc, res, options) { + + const { isPatching } = options || {}; + + + if ((!isPatching || typeof(doc.date) !== 'undefined') + + && (typeof(doc.date) !== 'number' + || doc.date <= apiConst.MIN_TIMESTAMP) + ) { + return sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_FIELD_DATE); + } + + + if ((!isPatching || typeof(doc.utcOffset) !== 'undefined') + + && (typeof(doc.utcOffset) !== 'number' + || doc.utcOffset < apiConst.MIN_UTC_OFFSET + || doc.utcOffset > apiConst.MAX_UTC_OFFSET) + ) { + return sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_FIELD_UTC); + } + + + if ((!isPatching || typeof(doc.app) !== 'undefined') + + && (typeof(doc.app) !== 'string' + || stringTools.isNullOrWhitespace(doc.app)) + ) { + return sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_FIELD_APP); + } + + return true; +} + + +/** + * Calculate identifier for the document + * @param {Object} doc + * @returns string + */ +function calculateIdentifier (doc) { + if (!doc) + return undefined; + + let key = doc.device + '_' + doc.date; + if (doc.eventType) { + key += '_' + doc.eventType; + } + + return uuidv5(key, uuidNamespace); +} + + +/** + * Validate identifier in the document + * @param {Object} doc + */ +function resolveIdentifier (doc) { + + let identifier = calculateIdentifier(doc); + if (doc.identifier) { + if (doc.identifier !== identifier) { + console.warn(`APIv3: Identifier mismatch (expected: ${identifier}, received: ${doc.identifier})`); + console.log(doc); + } + } + else { + doc.identifier = identifier; + } +} + + +module.exports = { + sendJSONStatus, + validateCommon, + calculateIdentifier, + resolveIdentifier +}; \ No newline at end of file diff --git a/lib/api3/shared/storageTools.js b/lib/api3/shared/storageTools.js new file mode 100644 index 00000000000..b7d9dca6776 --- /dev/null +++ b/lib/api3/shared/storageTools.js @@ -0,0 +1,63 @@ +'use strict'; + +function getStorageVersion (app) { + + return new Promise(function (resolve, reject) { + + try { + const storage = app.get('entriesCollection').storage; + let storageVersion = app.get('storageVersion'); + + if (storageVersion) { + process.nextTick(() => { + resolve(storageVersion); + }); + } else { + storage.version() + .then(storageVersion => { + + app.set('storageVersion', storageVersion); + resolve(storageVersion); + }, reject); + } + } catch (error) { + reject(error); + } + }); +} + + +function getVersionInfo(app) { + + return new Promise(function (resolve, reject) { + + try { + const srvDate = new Date() + , info = { version: app.get('version') + , apiVersion: app.get('apiVersion') + , srvDate: srvDate.getTime() + }; + + getStorageVersion(app) + .then(storageVersion => { + + if (!storageVersion) + throw new Error('empty storageVersion'); + + info.storage = storageVersion; + + resolve(info); + + }, reject); + + } catch(error) { + reject(error); + } + }); +} + + +module.exports = { + getStorageVersion, + getVersionInfo +}; diff --git a/lib/api3/shared/stringTools.js b/lib/api3/shared/stringTools.js new file mode 100644 index 00000000000..b71a4b4f1a6 --- /dev/null +++ b/lib/api3/shared/stringTools.js @@ -0,0 +1,28 @@ +'use strict'; + +/** + * Check the string for strictly valid number (no other characters present) + * @param {any} str + */ +function isNumberInString (str) { + return !isNaN(parseFloat(str)) && isFinite(str); +} + + +/** + * Check the string for non-whitespace characters presence + * @param {any} input + */ +function isNullOrWhitespace (input) { + + if (typeof input === 'undefined' || input == null) return true; + + return input.replace(/\s/g, '').length < 1; +} + + + +module.exports = { + isNumberInString, + isNullOrWhitespace +}; diff --git a/lib/api3/specific/lastModified.js b/lib/api3/specific/lastModified.js new file mode 100644 index 00000000000..b27ecaca852 --- /dev/null +++ b/lib/api3/specific/lastModified.js @@ -0,0 +1,101 @@ +'use strict'; + +function configure (app, ctx, env) { + const express = require('express') + , api = express.Router( ) + , apiConst = require('../const.json') + , security = require('../security') + , opTools = require('../shared/operationTools') + ; + + api.get('/lastModified', async function getLastModified (req, res) { + + async function getLastModified (col) { + + let result; + const lastModified = await col.storage.getLastModified('srvModified'); + + if (lastModified) { + result = lastModified.srvModified ? lastModified.srvModified : null; + } + + if (col.fallbackDateField) { + + const lastModified = await col.storage.getLastModified(col.fallbackDateField); + + if (lastModified && lastModified[col.fallbackDateField]) { + let timestamp = lastModified[col.fallbackDateField]; + if (typeof(timestamp) === 'string') { + timestamp = (new Date(timestamp)).getTime(); + } + + if (result === null || result < timestamp) { + result = timestamp; + } + } + } + + return { colName: col.colName, lastModified: result }; + } + + + async function collectionsAsync (auth) { + + const cols = app.get('collections') + , promises = [] + , output = {} + ; + + for (const colName in cols) { + const col = cols[colName]; + + if (security.checkPermission(auth, 'api:' + col.colName + ':read')) { + promises.push(getLastModified(col)); + } + } + + const results = await Promise.all(promises); + + for (const result of results) { + if (result.lastModified) + output[result.colName] = result.lastModified; + } + + return output; + } + + + async function operation (opCtx) { + + const { res, auth } = opCtx; + const srvDate = new Date(); + + let info = { + srvDate: srvDate.getTime(), + collections: { } + }; + + info.collections = await collectionsAsync(auth); + + res.json(info); + } + + + const opCtx = { app, ctx, env, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + await operation(opCtx); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }); + + return api; +} +module.exports = configure; diff --git a/lib/api3/specific/status.js b/lib/api3/specific/status.js new file mode 100644 index 00000000000..7b70b24ab71 --- /dev/null +++ b/lib/api3/specific/status.js @@ -0,0 +1,71 @@ +'use strict'; + +function configure (app, ctx, env) { + const express = require('express') + , api = express.Router( ) + , apiConst = require('../const.json') + , storageTools = require('../shared/storageTools') + , security = require('../security') + , opTools = require('../shared/operationTools') + ; + + api.get('/status', async function getStatus (req, res) { + + function permsForCol (col, auth) { + let colPerms = ''; + + if (security.checkPermission(auth, 'api:' + col.colName + ':create')) { + colPerms += 'c'; + } + + if (security.checkPermission(auth, 'api:' + col.colName + ':read')) { + colPerms += 'r'; + } + + if (security.checkPermission(auth, 'api:' + col.colName + ':update')) { + colPerms += 'u'; + } + + if (security.checkPermission(auth, 'api:' + col.colName + ':delete')) { + colPerms += 'd'; + } + + return colPerms; + } + + + async function operation (opCtx) { + const cols = app.get('collections'); + + let info = await storageTools.getVersionInfo(app); + + info.apiPermissions = {}; + for (let col in cols) { + const colPerms = permsForCol(col, opCtx.auth); + if (colPerms) { + info.apiPermissions[col] = colPerms; + } + } + + res.json(info); + } + + + const opCtx = { app, ctx, env, req, res }; + + try { + opCtx.auth = await security.authenticate(opCtx); + + await operation(opCtx); + + } catch (err) { + console.error(err); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }); + + return api; +} +module.exports = configure; diff --git a/lib/api3/specific/version.js b/lib/api3/specific/version.js new file mode 100644 index 00000000000..25392fe99d7 --- /dev/null +++ b/lib/api3/specific/version.js @@ -0,0 +1,28 @@ +'use strict'; + +function configure (app) { + const express = require('express') + , api = express.Router( ) + , apiConst = require('../const.json') + , storageTools = require('../shared/storageTools') + , opTools = require('../shared/operationTools') + ; + + api.get('/version', async function getVersion (req, res) { + + try { + const versionInfo = await storageTools.getVersionInfo(app); + + res.json(versionInfo); + + } catch(error) { + console.error(error); + if (!res.headersSent) { + return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR); + } + } + }); + + return api; +} +module.exports = configure; diff --git a/lib/api3/storage/mongoCollection/find.js b/lib/api3/storage/mongoCollection/find.js new file mode 100644 index 00000000000..bc399dbce98 --- /dev/null +++ b/lib/api3/storage/mongoCollection/find.js @@ -0,0 +1,93 @@ +'use strict'; + +const utils = require('./utils') + , _ = require('lodash') + ; + + +/** + * Find single document by identifier + * @param {Object} col + * @param {string} identifier + * @param {Object} projection + */ +function findOne (col, identifier, projection) { + + return new Promise(function (resolve, reject) { + + const filter = utils.filterForOne(identifier); + + col.find(filter) + .project(projection) + .sort({ identifier: -1 }) // document with identifier first (not the fallback one) + .toArray(function mongoDone (err, result) { + + if (err) { + reject(err); + } else { + _.each(result, utils.normalizeDoc); + resolve(result); + } + }); + }); +} + + +/** + * Find single document by query filter + * @param {Object} col + * @param {Object} filter specific filter + * @param {Object} projection + */ +function findOneFilter (col, filter, projection) { + + return new Promise(function (resolve, reject) { + + col.find(filter) + .project(projection) + .sort({ identifier: -1 }) // document with identifier first (not the fallback one) + .toArray(function mongoDone (err, result) { + + if (err) { + reject(err); + } else { + _.each(result, utils.normalizeDoc); + resolve(result); + } + }); + }); +} + + +/** + * Find many documents matching the filtering criteria + */ +function findMany (col, filterDef, sort, limit, skip, projection, onlyValid, logicalOperator = 'and') { + + return new Promise(function (resolve, reject) { + + const filter = utils.parseFilter(filterDef, logicalOperator, onlyValid); + + col.find(filter) + .sort(sort) + .limit(limit) + .skip(skip) + .project(projection) + .toArray(function mongoDone (err, result) { + + if (err) { + reject(err); + } else { + _.each(result, utils.normalizeDoc); + resolve(result); + } + }); + }); +} + + +module.exports = { + findOne, + findOneFilter, + findMany +}; \ No newline at end of file diff --git a/lib/api3/storage/mongoCollection/index.js b/lib/api3/storage/mongoCollection/index.js new file mode 100644 index 00000000000..e6ad0a6cf8b --- /dev/null +++ b/lib/api3/storage/mongoCollection/index.js @@ -0,0 +1,90 @@ +'use strict'; + +/** + * Storage implementation using mongoDB + * @param {Object} ctx + * @param {Object} env + * @param {string} colName - name of the collection in mongo database + */ +function MongoCollection (ctx, env, colName) { + + const self = this + , utils = require('./utils') + , find = require('./find') + , modify = require('./modify') + ; + + self.colName = colName; + + self.col = ctx.store.collection(colName); + + ctx.store.ensureIndexes(self.col, [ 'identifier', + 'srvModified', + 'isValid' + ]); + + + self.identifyingFilter = utils.identifyingFilter; + + self.findOne = (...args) => find.findOne(self.col, ...args); + + self.findOneFilter = (...args) => find.findOneFilter(self.col, ...args); + + self.findMany = (...args) => find.findMany(self.col, ...args); + + self.insertOne = (...args) => modify.insertOne(self.col, ...args); + + self.replaceOne = (...args) => modify.replaceOne(self.col, ...args); + + self.updateOne = (...args) => modify.updateOne(self.col, ...args); + + self.deleteOne = (...args) => modify.deleteOne(self.col, ...args); + + self.deleteManyOr = (...args) => modify.deleteManyOr(self.col, ...args); + + + /** + * Get server version + */ + self.version = function version () { + + return new Promise(function (resolve, reject) { + + ctx.store.db.admin().buildInfo({}, function mongoDone (err, result) { + + err + ? reject(err) + : resolve({ + storage: 'mongodb', + version: result.version + }); + }); + }); + }; + + + /** + * Get timestamp (e.g. srvModified) of the last modified document + */ + self.getLastModified = function getLastModified (fieldName) { + + return new Promise(function (resolve, reject) { + + self.col.find() + + .sort({ [fieldName]: -1 }) + + .limit(1) + + .project({ [fieldName]: 1 }) + + .toArray(function mongoDone (err, [ result ]) { + err + ? reject(err) + : resolve(result); + }); + }); + } +} + +module.exports = MongoCollection; \ No newline at end of file diff --git a/lib/api3/storage/mongoCollection/modify.js b/lib/api3/storage/mongoCollection/modify.js new file mode 100644 index 00000000000..6552fe40e8c --- /dev/null +++ b/lib/api3/storage/mongoCollection/modify.js @@ -0,0 +1,123 @@ +'use strict'; + +const utils = require('./utils'); + + +/** + * Insert single document + * @param {Object} col + * @param {Object} doc + */ +function insertOne (col, doc) { + + return new Promise(function (resolve, reject) { + + col.insertOne(doc, function mongoDone(err, result) { + + if (err) { + reject(err); + } else { + const identifier = doc.identifier || result.insertedId.toString(); + delete doc._id; + resolve(identifier); + } + }); + }); +} + + +/** + * Replace single document + * @param {Object} col + * @param {string} identifier + * @param {Object} doc + */ +function replaceOne (col, identifier, doc) { + + return new Promise(function (resolve, reject) { + + const filter = utils.filterForOne(identifier); + + col.replaceOne(filter, doc, { }, function mongoDone(err, result) { + if (err) { + reject(err); + } else { + resolve(result.matchedCount); + } + }); + }); +} + + +/** + * Update single document by identifier + * @param {Object} col + * @param {string} identifier + * @param {object} setFields + */ +function updateOne (col, identifier, setFields) { + + return new Promise(function (resolve, reject) { + + const filter = utils.filterForOne(identifier); + + col.updateOne(filter, { $set: setFields }, function mongoDone(err, result) { + if (err) { + reject(err); + } else { + resolve({ updated: result.result.nModified }); + } + }); + }); +} + + +/** + * Permanently remove single document by identifier + * @param {Object} col + * @param {string} identifier + */ +function deleteOne (col, identifier) { + + return new Promise(function (resolve, reject) { + + const filter = utils.filterForOne(identifier); + + col.deleteOne(filter, function mongoDone(err, result) { + if (err) { + reject(err); + } else { + resolve({ deleted: result.result.n }); + } + }); + }); +} + + +/** + * Permanently remove many documents matching any of filtering criteria + */ +function deleteManyOr (col, filterDef) { + + return new Promise(function (resolve, reject) { + + const filter = utils.parseFilter(filterDef, 'or'); + + col.deleteMany(filter, function mongoDone(err, result) { + if (err) { + reject(err); + } else { + resolve({ deleted: result.deletedCount }); + } + }); + }); +} + + +module.exports = { + insertOne, + replaceOne, + updateOne, + deleteOne, + deleteManyOr +}; \ No newline at end of file diff --git a/lib/api3/storage/mongoCollection/utils.js b/lib/api3/storage/mongoCollection/utils.js new file mode 100644 index 00000000000..1b2ab5610d7 --- /dev/null +++ b/lib/api3/storage/mongoCollection/utils.js @@ -0,0 +1,178 @@ +'use strict'; + +const _ = require('lodash') + , checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$") + , ObjectID = require('mongodb').ObjectID +; + + +/** + * Normalize document (make it mongoDB independent) + * @param {Object} doc - document loaded from mongoDB + */ +function normalizeDoc (doc) { + if (!doc.identifier) { + doc.identifier = doc._id.toString(); + } + + delete doc._id; +} + + +/** + * Parse filter definition array into mongoDB filtering object + * @param {any} filterDef + * @param {string} logicalOperator + * @param {bool} onlyValid + */ +function parseFilter (filterDef, logicalOperator, onlyValid) { + + let filter = { }; + if (!filterDef) + return filter; + + if (!_.isArray(filterDef)) { + return filterDef; + } + + let clauses = []; + + for (const itemDef of filterDef) { + let item; + + switch (itemDef.operator) { + case 'eq': + item = itemDef.value; + break; + + case 'ne': + item = { $ne: itemDef.value }; + break; + + case 'gt': + item = { $gt: itemDef.value }; + break; + + case 'gte': + item = { $gte: itemDef.value }; + break; + + case 'lt': + item = { $lt: itemDef.value }; + break; + + case 'lte': + item = { $lte: itemDef.value }; + break; + + case 'in': + item = { $in: itemDef.value.toString().split('|') }; + break; + + case 'nin': + item = { $nin: itemDef.value.toString().split('|') }; + break; + + case 're': + item = { $regex: itemDef.value.toString() }; + break; + + default: + throw new Error('Unsupported or missing filter operator ' + itemDef.operator); + } + + if (logicalOperator === 'or') { + let clause = { }; + clause[itemDef.field] = item; + clauses.push(clause); + } + else { + filter[itemDef.field] = item; + } + } + + if (logicalOperator === 'or') { + filter = { $or: clauses }; + } + + if (onlyValid) { + filter.isValid = { $ne: false }; + } + + return filter; +} + + +/** + * Create query filter for single document with identifier fallback + * @param {string} identifier + */ +function filterForOne (identifier) { + + const filterOpts = [ { identifier } ]; + + // fallback to "identifier = _id" + if (checkForHexRegExp.test(identifier)) { + filterOpts.push({ _id: ObjectID(identifier) }); + } + + return { $or: filterOpts }; +} + + +/** + * Create query filter to check whether the document already exists in the storage. + * This function resolves eventual fallback deduplication. + * @param {string} identifier - identifier of document to check its existence in the storage + * @param {Object} doc - document to check its existence in the storage + * @param {Array} dedupFallbackFields - fields that all need to be matched to identify document via fallback deduplication + * @returns {Object} - query filter for mongo or null in case of no identifying possibility + */ +function identifyingFilter (identifier, doc, dedupFallbackFields) { + + const filterItems = []; + + if (identifier) { + // standard identifier field (APIv3) + filterItems.push({ identifier: identifier }); + + // fallback to "identifier = _id" (APIv1) + if (checkForHexRegExp.test(identifier)) { + filterItems.push({ identifier: { $exists: false }, _id: ObjectID(identifier) }); + } + } + + // let's deal with eventual fallback deduplication + if (!_.isEmpty(doc) && _.isArray(dedupFallbackFields) && dedupFallbackFields.length > 0) { + let dedupFilterItems = []; + + _.each(dedupFallbackFields, function addDedupField (field) { + + if (doc[field] !== undefined) { + + let dedupFilterItem = { }; + dedupFilterItem[field] = doc[field]; + dedupFilterItems.push(dedupFilterItem); + } + }); + + if (dedupFilterItems.length === dedupFallbackFields.length) { // all dedup fields are present + + dedupFilterItems.push({ identifier: { $exists: false } }); // force not existing identifier for fallback deduplication + filterItems.push({ $and: dedupFilterItems }); + } + } + + if (filterItems.length > 0) + return { $or: filterItems }; + else + return null; // we don't have any filtering rule to identify the document in the storage +} + + +module.exports = { + normalizeDoc, + parseFilter, + filterForOne, + identifyingFilter +}; \ No newline at end of file diff --git a/lib/api3/storageSocket.js b/lib/api3/storageSocket.js new file mode 100644 index 00000000000..e8c08310d2b --- /dev/null +++ b/lib/api3/storageSocket.js @@ -0,0 +1,145 @@ +'use strict'; + +const apiConst = require('./const'); + +/** + * Socket.IO broadcaster of any storage change + */ +function StorageSocket (app, env, ctx) { + + const self = this; + + const LOG_GREEN = '\x1B[32m' + , LOG_MAGENTA = '\x1B[35m' + , LOG_RESET = '\x1B[0m' + , LOG = LOG_GREEN + 'STORAGE SOCKET: ' + LOG_RESET + , LOG_ERROR = LOG_MAGENTA + 'STORAGE SOCKET: ' + LOG_RESET + , NAMESPACE = '/storage' + ; + + + /** + * Initialize socket namespace and bind the events + * @param {Object} io Socket.IO object to multiplex namespaces + */ + self.init = function init (io) { + self.io = io; + + self.namespace = io.of(NAMESPACE); + self.namespace.on('connection', function onConnected (socket) { + + const remoteIP = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress; + console.log(LOG + 'Connection from client ID: ', socket.client.id, ' IP: ', remoteIP); + + socket.on('disconnect', function onDisconnect () { + console.log(LOG + 'Disconnected client ID: ', socket.client.id); + }); + + socket.on('subscribe', function onSubscribe (message, returnCallback) { + self.subscribe(socket, message, returnCallback); + }); + }); + + ctx.bus.on('storage-socket-create', self.emitCreate); + ctx.bus.on('storage-socket-update', self.emitUpdate); + ctx.bus.on('storage-socket-delete', self.emitDelete); + }; + + + /** + * Authorize Socket.IO client and subscribe him to authorized rooms + * @param {Object} socket + * @param {Object} message input message from the client + * @param {Function} returnCallback function for returning a value back to the client + */ + self.subscribe = function subscribe (socket, message, returnCallback) { + const shouldCallBack = typeof(returnCallback) === 'function'; + + if (message && message.accessToken) { + return ctx.authorization.resolveAccessToken(message.accessToken, function resolveFinish (err, auth) { + if (err) { + console.log(`${LOG_ERROR} Authorization failed for accessToken:`, message.accessToken); + + if (shouldCallBack) { + returnCallback({ success: false, message: apiConst.MSG.SOCKET_MISSING_OR_BAD_ACCESS_TOKEN }); + } + return err; + } + else { + return self.subscribeAuthorized(socket, message, auth, returnCallback); + } + }); + } + + console.log(`${LOG_ERROR} Authorization failed for message:`, message); + if (shouldCallBack) { + returnCallback({ success: false, message: apiConst.MSG.SOCKET_MISSING_OR_BAD_ACCESS_TOKEN}); + } + }; + + + /** + * Subscribe already authorized Socket.IO client to his rooms + * @param {Object} socket + * @param {Object} message input message from the client + * @param {Object} auth authorization of the client + * @param {Function} returnCallback function for returning a value back to the client + */ + self.subscribeAuthorized = function subscribeAuthorized (socket, message, auth, returnCallback) { + const shouldCallBack = typeof(returnCallback) === 'function'; + const enabledCols = app.get('enabledCollections'); + const cols = Array.isArray(message.collections) ? message.collections : enabledCols; + const subscribed = []; + + for (const col of cols) { + if (enabledCols.includes(col)) { + const permission = (col === 'settings') ? `api:${col}:admin` : `api:${col}:read`; + + if (ctx.authorization.checkMultiple(permission, auth.shiros)) { + socket.join(col); + subscribed.push(col); + } + } + } + + const doc = subscribed.length > 0 + ? { success: true, collections: subscribed } + : { success: false, message: apiConst.MSG.SOCKET_UNAUTHORIZED_TO_ANY }; + if (shouldCallBack) { + returnCallback(doc); + } + return doc; + }; + + + /** + * Emit create event to the subscribers (of the collection's room) + * @param {Object} event + */ + self.emitCreate = function emitCreate (event) { + self.namespace.to(event.colName) + .emit('create', event); + }; + + + /** + * Emit update event to the subscribers (of the collection's room) + * @param {Object} event + */ + self.emitUpdate = function emitUpdate (event) { + self.namespace.to(event.colName) + .emit('update', event); + }; + + + /** + * Emit delete event to the subscribers (of the collection's room) + * @param {Object} event + */ + self.emitDelete = function emitDelete (event) { + self.namespace.to(event.colName) + .emit('delete', event); + } +} + +module.exports = StorageSocket; \ No newline at end of file diff --git a/lib/api3/swagger.js b/lib/api3/swagger.js new file mode 100644 index 00000000000..2d434e97f53 --- /dev/null +++ b/lib/api3/swagger.js @@ -0,0 +1,41 @@ +'use strict'; + +const express = require('express') + , fs = require('fs') + ; + + +function setupSwaggerUI (app) { + + const serveSwaggerDef = function serveSwaggerDef (req, res) { + res.sendFile(__dirname + '/swagger.yaml'); + }; + app.get('/swagger.yaml', serveSwaggerDef); + + const swaggerUiAssetPath = require('swagger-ui-dist').getAbsoluteFSPath(); + const swaggerFiles = express.static(swaggerUiAssetPath); + + const urlRegex = /url: "[^"]*",/; + + const patchIndex = function patchIndex (req, res) { + const indexContent = fs.readFileSync(`${swaggerUiAssetPath}/index.html`) + .toString() + .replace(urlRegex, 'url: "../swagger.yaml",'); + res.send(indexContent); + }; + + app.get('/swagger-ui-dist', function getSwaggerRoot (req, res) { + let targetUrl = req.originalUrl; + if (!targetUrl.endsWith('/')) { + targetUrl += '/'; + } + targetUrl += 'index.html'; + res.redirect(targetUrl); + }); + app.get('/swagger-ui-dist/index.html', patchIndex); + + app.use('/swagger-ui-dist', swaggerFiles); +} + + +module.exports = setupSwaggerUI; \ No newline at end of file diff --git a/lib/api3/swagger.yaml b/lib/api3/swagger.yaml new file mode 100644 index 00000000000..17db893e0ef --- /dev/null +++ b/lib/api3/swagger.yaml @@ -0,0 +1,1614 @@ +openapi: 3.0.0 +servers: + - url: '/api/v3' +info: + version: '3.0.1' + title: Nightscout API + contact: + name: NS development discussion channel + url: https://gitter.im/nightscout/public + license: + name: AGPL 3 + url: 'https://www.gnu.org/licenses/agpl.txt' + description: + Nightscout API v3 is a component of cgm-remote-monitor project. It aims to provide lightweight, secured and HTTP REST compliant interface for your T1D treatment data exchange. + + + API v3 uses these environment variables, among other things: + + - Security switch (optional, default = `true`) +
API3_SECURITY_ENABLE=true
+ You can turn the whole security mechanism off, e.g. for debugging or development purposes, + but this should never be set to false in production. + + + - Number of minutes of acceptable time skew between client's and server's clock (optional, default = 5) +
API3_TIME_SKEW_TOLERANCE=5
+ This security parameter is used for preventing anti-replay attacks, specifically when checking the time from `Date` header. + + + - Maximum limit count of documents retrieved from single query +
API3_MAX_LIMIT=1000
+ + + - Autopruning of obsolete documents (optional, default is only `DEVICESTATUS`=60) +
API3_AUTOPRUNE_DEVICESTATUS=60
+
+      API3_AUTOPRUNE_ENTRIES=365
+
+      API3_AUTOPRUNE_TREATMENTS=120
+      
+ You can specify for which collections autopruning will be activated and length of retention period in days, e.g. "Hold 60 days of devicestatus, automatically delete older documents, hold 365 days of treatments and entries, automatically delete older documents." + + + - Fallback deduplication switch (optional, default = true) +
API3_DEDUP_FALLBACK_ENABLED=true
+ API3 uses the `identifier` field for document identification and mutual distinction within a single collection. There is automatic deduplication implemented matching the equal `identifier` field. E.g. `CREATE` operation for document having the same `identifier` as another one existing in the database is automatically transformed into `UPDATE` operation of the document found in the database. + + Documents not created via API v3 usually does not have any `identifier` field, but we would like to have some form of deduplication for them, too. This fallback deduplication is turned on by having set `API3_DEDUP_FALLBACK_ENABLED` to `true`. + When searching the collection in database, the document is found to be a duplicate only when either he has equal `identifier` or he has no `identifier` and meets: +
`devicestatus` collection: equal combination of `created_at` and `device`
+
+      `entries` collection:      equal combination of `date` and `type`
+
+      `food` collection:         equal `created_at`
+
+      `profile` collection:      equal `created_at`
+
+      `treatments` collection:   equal combination of `created_at` and `eventType`
+      
+ + + - Fallback switch for adding `created_at` field along the `date` field (optional, default = true) +
API3_CREATED_AT_FALLBACK_ENABLED=true
+ Standard APIv3 document model uses only `date` field for storing a timestamp of the event recorded by the document. But there is a fallback option to fill `created_at` field as well automatically on each insert/update, just to keep all older components working. + +tags: + - name: generic + description: Generic operations with each database collection (devicestatus, entries, food, profile, settings, treatments) + - name: other + description: All other various operations + + +paths: + /{collection}: + parameters: + - in: path + name: collection + description: Collection to which the operation is targeted + required: true + schema: + $ref: '#/components/schemas/paramCollection' + + - $ref: '#/components/parameters/dateHeader' + - $ref: '#/components/parameters/nowParam' + - $ref: '#/components/parameters/tokenParam' + + ###################################################################################### + get: + tags: + - generic + summary: 'SEARCH: Search documents from the collection' + operationId: SEARCH + description: General search operation through documents of one collection, matching the specified filtering criteria. You can apply: + + + 1) filtering - combining any number of filtering parameters + + + 2) ordering - using `sort` or `sort$desc` parameter + + + 3) paging - using `limit` and `skip` parameters + + + When there is no document matching the filtering criteria, HTTP status 204 is returned with empty response content. Otherwise HTTP 200 code is returned with JSON array of matching documents as a response content. + + + This operation requires `read` permission for the API and the collection (e.g. `*:*:read`, `api:*:read`, `*:treatments:read`, `api:treatments:read`). + + + The only exception is the `settings` collection which requires `admin` permission (`api:settings:admin`), because the settings of each application should be isolated and kept secret. You need to know the concrete identifier to access the app's settings. + + + parameters: + - $ref: '#/components/parameters/filterParams' + - $ref: '#/components/parameters/sortParam' + - $ref: '#/components/parameters/sortDescParam' + - $ref: '#/components/parameters/limitParam' + - $ref: '#/components/parameters/skipParam' + - $ref: '#/components/parameters/fieldsParam' + + security: + - apiKeyAuth: [] + + responses: + 200: + $ref: '#/components/responses/search200' + 204: + $ref: '#/components/responses/search204' + 400: + $ref: '#/components/responses/400BadRequest' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + 404: + $ref: '#/components/responses/404NotFound' + + + ###################################################################################### + post: + tags: + - generic + summary: 'CREATE: Inserts a new document into the collection' + description: + Using this operation you can insert new documents into collection. Normally the operation ends with 201 HTTP status code, `Last-Modified` and `Location` headers specified and with an empty response content. `identifier` can be parsed from the `Location` response header. + + + When the document to post is marked as a duplicate (using rules described at `API3_DEDUP_FALLBACK_ENABLED` switch), the update operation takes place instead of inserting. In this case the original document in the collection is found and it gets updated by the actual operation POST body. Finally the operation ends with 204 HTTP status code along with `Last-Modified` and correct `Location` headers. + + + This operation provides autopruning of the collection (if autopruning is enabled). + + + This operation requires `create` (and/or `update` for deduplication) permission for the API and the collection (e.g. `api:treatments:create` and `api:treatments:update`) + + requestBody: + description: JSON with new document to insert + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DocumentToPost' + + security: + - apiKeyAuth: [] + + responses: + 201: + $ref: '#/components/responses/201CreatedLocation' + 204: + $ref: '#/components/responses/204NoContentLocation' + 400: + $ref: '#/components/responses/400BadRequest' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + 404: + $ref: '#/components/responses/404NotFound' + 422: + $ref: '#/components/responses/422UnprocessableEntity' + + + #return HTTP STATUS 400 for all other verbs (PUT, PATCH, DELETE,...) + + + /{collection}/{identifier}: + parameters: + - in: path + name: collection + description: Collection to which the operation is targeted + required: true + schema: + $ref: '#/components/schemas/paramCollection' + - in: path + name: identifier + description: Identifier of the document to which the operation is targeted + required: true + schema: + $ref: '#/components/schemas/paramIdentifier' + + - $ref: '#/components/parameters/dateHeader' + - $ref: '#/components/parameters/nowParam' + - $ref: '#/components/parameters/tokenParam' + + ###################################################################################### + get: + tags: + - generic + summary: 'READ: Retrieves a single document from the collection' + description: + Basically this operation looks for a document matching the `identifier` field returning 200 or 404 HTTP status code. + + + If the document has been found in the collection but it had already been deleted, 410 HTTP status code with empty response content is to be returned. + + + When `If-Modified-Since` header is used and its value is greater than the timestamp of the document in the collection, 304 HTTP status code with empty response content is returned. It means that the document has not been modified on server since the last retrieval to client side. + With `If-Modified-Since` header and less or equal timestamp `srvModified` a normal 200 HTTP status with full response is returned. + + + This operation requires `read` permission for the API and the collection (e.g. `api:treatments:read`) + + parameters: + - $ref: '#/components/parameters/ifModifiedSinceHeader' + - $ref: '#/components/parameters/fieldsParam' + + security: + - apiKeyAuth: [] + + responses: + 200: + $ref: '#/components/responses/read200' + 304: + $ref: '#/components/responses/304NotModified' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + 404: + $ref: '#/components/responses/404NotFound' + 410: + $ref: '#/components/responses/410Gone' + + + ###################################################################################### + put: + tags: + - generic + summary: 'UPDATE: Updates a document in the collection' + description: + Normally the document with the matching `identifier` will be replaced in the collection by the whole JSON request body and 204 HTTP status code will be returned with empty response body. + + + If the document has been found in the collection but it had already been deleted, 410 HTTP status code with empty response content is to be returned. + + + When no document with `identifier` has been found in the collection, then an insert operation takes place instead of updating. Finally 201 HTTP status code is returned with only `Last-Modified` header (`identifier` is already known from the path parameter). + + + You can also specify `If-Unmodified-Since` request header including your timestamp of document's last modification. If the document has been modified by somebody else on the server afterwards (and you do not know about it), the 412 HTTP status code is returned cancelling the update operation. You can use this feature to prevent race condition problems. + + + This operation provides autopruning of the collection (if autopruning is enabled). + + + This operation requires `update` (and/or `create`) permission for the API and the collection (e.g. `api:treatments:update` and `api:treatments:create`) + + parameters: + - $ref: '#/components/parameters/ifUnmodifiedSinceHeader' + + requestBody: + description: JSON of new version of document (`identifier` in JSON is ignored if present) + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DocumentToPost' + + security: + - apiKeyAuth: [] + + responses: + 201: + $ref: '#/components/responses/201Created' + 204: + $ref: '#/components/responses/204NoContentLocation' + 400: + $ref: '#/components/responses/400BadRequest' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + 404: + $ref: '#/components/responses/404NotFound' + 412: + $ref: '#/components/responses/412PreconditionFailed' + 410: + $ref: '#/components/responses/410Gone' + 422: + $ref: '#/components/responses/422UnprocessableEntity' + + + ###################################################################################### + patch: + tags: + - generic + summary: 'PATCH: Partially updates document in the collection' + description: + Normally the document with the matching `identifier` will be retrieved from the collection and it will be patched by all specified fields from the JSON request body. Finally 204 HTTP status code will be returned with empty response body. + + + If the document has been found in the collection but it had already been deleted, 410 HTTP status code with empty response content is to be returned. + + + When no document with `identifier` has been found in the collection, then the operation ends with 404 HTTP status code. + + + You can also specify `If-Unmodified-Since` request header including your timestamp of document's last modification. If the document has been modified by somebody else on the server afterwards (and you do not know about it), the 412 HTTP status code is returned cancelling the update operation. You can use this feature to prevent race condition problems. + + + `PATCH` operation can save some bandwidth for incremental document updates in comparison with `GET` - `UPDATE` operation sequence. + + + While patching the document, the field `modifiedBy` is automatically set to the authorized subject's name. + + + This operation provides autopruning of the collection (if autopruning is enabled). + + + This operation requires `update` permission for the API and the collection (e.g. `api:treatments:update`) + + parameters: + - $ref: '#/components/parameters/ifUnmodifiedSinceHeader' + + requestBody: + description: JSON of new version of document (`identifier` in JSON is ignored if present) + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DocumentToPost' + + security: + - apiKeyAuth: [] + + responses: + 204: + $ref: '#/components/responses/204NoContentLocation' + 400: + $ref: '#/components/responses/400BadRequest' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + 404: + $ref: '#/components/responses/404NotFound' + 412: + $ref: '#/components/responses/412PreconditionFailed' + 410: + $ref: '#/components/responses/410Gone' + 422: + $ref: '#/components/responses/422UnprocessableEntity' + + + ###################################################################################### + delete: + tags: + - generic + summary: 'DELETE: Deletes a document from the collection' + description: + If the document has already been deleted, the operation will succeed anyway. Normally, documents are not really deleted from the collection but they are only marked as deleted. For special cases the deletion can be irreversible using `permanent` parameter. + + + This operation provides autopruning of the collection (if autopruning is enabled). + + + This operation requires `delete` permission for the API and the collection (e.g. `api:treatments:delete`) + + + parameters: + - $ref: '#/components/parameters/permanentParam' + + security: + - apiKeyAuth: [] + + responses: + 204: + description: Successful operation - empty response + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + 404: + $ref: '#/components/responses/404NotFound' + 422: + $ref: '#/components/responses/422UnprocessableEntity' + + + ###################################################################################### + /{collection}/history: + parameters: + - in: path + name: collection + description: Collection to which the operation is targeted + required: true + schema: + $ref: '#/components/schemas/paramCollection' + + - $ref: '#/components/parameters/dateHeader' + - $ref: '#/components/parameters/nowParam' + - $ref: '#/components/parameters/tokenParam' + + get: + tags: + - generic + summary: 'HISTORY: Retrieves incremental changes since timestamp' + operationId: HISTORY + description: + HISTORY operation is intended for continuous data synchronization with other systems. + + Every insertion, update and deletion will be included in the resulting JSON array of documents (since timestamp in `Last-Modified` request header value). All changes are listed chronologically in response with 200 HTTP status code. The maximum listed `srvModified` timestamp is also stored in `Last-Modified` and `ETag` response headers that you can use for future, directly following synchronization. You can also limit the array's length using `limit` parameter. + + + Deleted documents will appear with `isValid` = `false` field. + + + When there is no change detected since the timestamp the operation ends with 204 HTTP status code and empty response content. + + + HISTORY operation has a fallback mechanism in place for documents, which were not created by API v3. For such documents `srvModified` is virtually assigned from the `date` field (for `entries` collection) or from the `created_at` field (for other collections). + + + This operation requires `read` permission for the API and the collection (e.g. `api:treatments:read`) + + + The only exception is the `settings` collection which requires `admin` permission (`api:settings:admin`), because the settings of each application should be isolated and kept secret. You need to know the concrete identifier to access the app's settings. + + + parameters: + - $ref: '#/components/parameters/lastModifiedRequiredHeader' + - $ref: '#/components/parameters/limitParam' + - $ref: '#/components/parameters/fieldsParam' + + security: + - apiKeyAuth: [] + + responses: + 200: + $ref: '#/components/responses/history200' + 204: + $ref: '#/components/responses/history204' + 400: + $ref: '#/components/responses/400BadRequest' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + 404: + $ref: '#/components/responses/404NotFound' + + + ###################################################################################### + /{collection}/history/{lastModified}: + parameters: + - in: path + name: collection + description: Collection to which the operation is targeted + required: true + schema: + $ref: '#/components/schemas/paramCollection' + + - in: path + name: lastModified + description: Starting timestamp (in UNIX epoch format, defined with respect to server's clock) since which the changes in documents are to be listed. Query for modified documents is made using "greater than" operator (not including equal timestamps). + required: true + schema: + type: integer + format: int64 + + - $ref: '#/components/parameters/dateHeader' + - $ref: '#/components/parameters/nowParam' + - $ref: '#/components/parameters/tokenParam' + + get: + tags: + - generic + summary: 'HISTORY: Retrieves incremental changes since timestamp' + operationId: HISTORY2 + description: + This HISTORY operation variant is more precise than the previous one with `Last-Modified` request HTTP header), because it does not loose milliseconds precision. + + + Since this variant queries for changed documents by timestamp precisely and exclusively, the last modified document does not repeat itself in following calls. That is the reason why is this variant more suitable for continuous synchronization with other systems. + + + This variant behaves quite the same as the previous one in all other aspects. + + + parameters: + - $ref: '#/components/parameters/limitParam' + - $ref: '#/components/parameters/fieldsParam' + + security: + - apiKeyAuth: [] + + responses: + 200: + $ref: '#/components/responses/history200' + 204: + $ref: '#/components/responses/history204' + 400: + $ref: '#/components/responses/400BadRequest' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + 404: + $ref: '#/components/responses/404NotFound' + + + ###################################################################################### + /version: + + get: + tags: + - other + summary: 'VERSION: Returns actual version information' + description: No authentication is needed for this commnad (it is public) + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/Version' + + + ###################################################################################### + /status: + + get: + tags: + - other + summary: 'STATUS: Returns actual version information and all permissions granted for API' + description: + This operation requires authorization in contrast with VERSION operation. + + security: + - apiKeyAuth: [] + + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + + ###################################################################################### + /lastModified: + parameters: + - $ref: '#/components/parameters/dateHeader' + - $ref: '#/components/parameters/nowParam' + - $ref: '#/components/parameters/tokenParam' + + get: + tags: + - other + summary: 'LAST MODIFIED: Retrieves timestamp of the last modification of every collection' + operationId: LAST-MODIFIED + description: + LAST MODIFIED operation inspects collections separately (in parallel) and for each of them it finds the date of any last modification (insertion, update, deletion). + + Not only `srvModified`, but also `date` and `created_at` fields are inspected (as a fallback to previous API). + + + This operation requires `read` permission for the API and the collections (e.g. `api:treatments:read`). For each collection the permission is checked separately, you will get timestamps only for those collections that you have access to. + + security: + - apiKeyAuth: [] + + responses: + 200: + $ref: '#/components/responses/lastModified200' + 401: + $ref: '#/components/responses/401Unauthorized' + 403: + $ref: '#/components/responses/403Forbidden' + +###################################################################################### +components: + + parameters: + + dateHeader: + in: header + name: Date + schema: + type: string + required: false + description: + Timestamp (defined by client's clock) when the HTTP request was constructed on client. + This mandatory header serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. + This can be set alternatively in `now` query parameter. + + Example: + + +
Date: Wed, 17 Oct 2018 05:13:00 GMT
+ + + nowParam: + in: query + name: now + schema: + type: integer + format: int64 + required: false + description: + Timestamp (defined by client's clock) when the HTTP request was constructed on client. + This mandatory parameter serves as an anti-replay precaution. After a period of time (specified by `API3_TIME_SKEW_TOLERANCE`) the message won't be valid any more and it will be denied with HTTP 401 Unauthorized code. + This can be set alternatively in `Date` header. + + + Example: + + +
now=1525383610088
+ + + tokenParam: + in: query + name: token + schema: + type: string + required: false + description: + An alternative way of authorization - passing accessToken in a query parameter. + + + Example: + + +
token=testadmin-bf2591231bd2c042
+ + + limitParam: + in: query + name: limit + schema: + type: integer + minimum: 1 + default: stored in API3_MAX_LIMIT environment variable (usually 1000) + example: 100 + description: Maximum number of documents to get in result array + + skipParam: + in: query + name: skip + schema: + type: integer + minimum: 0 + default: 0 + example: 0 + description: + Number of documents to skip from collection query before + loading them into result array (used for pagination) + + sortParam: + in: query + name: sort + schema: + type: string + required: false + description: + Field name by which the sorting of documents is performed. This parameter cannot be combined with `sort$desc` parameter. + + sortDescParam: + in: query + name: sort$desc + schema: + type: string + required: false + description: + Field name by which the descending (reverse) sorting of documents is performed. This parameter cannot be combined with `sort` parameter. + + permanentParam: + in: query + name: permanent + schema: + type: boolean + required: false + description: + If true, the deletion will be irreversible and it will not appear in `HISTORY` operation. Normally there is no reason for setting this flag. + + + fieldsParam: + in: query + name: fields + schema: + type: string + default: '_all' + required: false + examples: + all: + value: '_all' + summary: All fields will be returned (default behaviour) + customSet: + value: 'date,insulin' + summary: Only fields date and insulin will be returned + description: A chosen set of fields to return in response. Either you can enumerate specific fields of interest or use the predefined set. Sample parameter values: + + + _all: All fields will be returned (default value) + + + date,insulin: Only fields `date` and `insulin` will be returned + + + filterParams: + in: query + name: filter_parameters + schema: + type: string + description: + Any number of filtering operators. + + + Each filtering operator has name like `$`, e.g. `carbs$gt=2` which represents filtering rule "The field carbs must be present and greater than 2". + + + You can choose from operators: + + + `eq`=equals, `insulin$eq=1.5` + + + `ne`=not equals, `insulin$ne=1.5` + + + `gt`=greater than, `carbs$gt=30` + + + `gte`=greater than or equal, `carbs$gte=30` + + + `lt`=less than, `carbs$lt=30` + + + `lte`=less than or equal, `carbs$lte=30` + + + `in`=in specified set, `type$in=sgv|mbg|cal` + + + `nin`=not in specified set, `eventType$nin=Temp%20Basal|Temporary%20Target` + + + `re`=regex pattern, `eventType$re=Temp.%2A` + + + When filtering by field `date`, `created_at`, `srvModified` or `srvCreated`, you can choose from three input formats + + - Unix epoch in milliseconds (1525383610088) + + - Unix epoch in seconds (1525383610) + + - ISO 8601 with optional timezone ('2018-05-03T21:40:10.088Z' or '2018-05-03T23:40:10.088+02:00') + + + The date is always queried in a normalized form - UTC with zero offset and with the correct format (1525383610088 for `date`, '2018-05-03T21:40:10.088Z' for `created_at`). + + lastModifiedRequiredHeader: + in: header + name: Last-Modified + schema: + type: string + required: true + description: + Starting timestamp (defined with respect to server's clock) since which the changes in documents are to be listed, formatted as: + + + <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT + + + Example: + + +
Last-Modified: Wed, 17 Oct 2018 05:13:00 GMT
+ + + ifModifiedSinceHeader: + in: header + name: If-Modified-Since + schema: + type: string + required: false + description: + Timestamp (defined with respect to server's clock) of the last document modification formatted as: + + + <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT + + + If this header is present, the operation will compare its value with the srvModified timestamp of the document at first and the operation result then may differ. The srvModified timestamp was defined by server's clock. + + + Example: + + +
If-Modified-Since: Wed, 17 Oct 2018 05:13:00 GMT
+ + + ifUnmodifiedSinceHeader: + in: header + name: If-Unmodified-Since + schema: + type: string + required: false + description: + Timestamp (defined with respect to server's clock) of the last document modification formatted as: + + + <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT + + + If this header is present, the operation will compare its value with the srvModified timestamp of the document at first and the operation result then may differ. The srvModified timestamp was defined by server's clock. + + + Example: + + +
If-Unmodified-Since: Wed, 17 Oct 2018 05:13:00 GMT
+ + + ###################################################################################### + responses: + + 201Created: + description: Successfully created a new document in collection + headers: + 'Last-Modified': + $ref: '#/components/schemas/headerLastModified' + + 201CreatedLocation: + description: Successfully created a new document in collection + headers: + 'Last-Modified': + $ref: '#/components/schemas/headerLastModified' + 'Location': + $ref: '#/components/schemas/headerLocation' + + 204NoContent: + description: Successfully finished operation + headers: + 'Last-Modified': + $ref: '#/components/schemas/headerLastModified' + + 204NoContentLocation: + description: Successfully finished operation + headers: + 'Last-Modified': + $ref: '#/components/schemas/headerLastModified' + 'Location': + $ref: '#/components/schemas/headerLocation' + + 304NotModified: + description: The document has not been modified on the server since timestamp specified in If-Modified-Since header + headers: + 'Last-Modified': + $ref: '#/components/schemas/headerLastModified' + + 400BadRequest: + description: The request is malformed. There may be some required parameters missing or there are unrecognized parameters present. + + 401Unauthorized: + description: The request was not successfully authenticated using access token or JWT, or the request has missing `Date` header or it contains an expired timestamp, so that the request cannot continue due to the security policy. + + 403Forbidden: + description: Insecure HTTP scheme used or the request has been successfully authenticated, but the security subject is not authorized for the operation. + + 404NotFound: + description: The collection or document specified was not found. + + 412PreconditionFailed: + description: The document has already been modified on the server since specified timestamp (in If-Unmodified-Since header). + + 410Gone: + description: The requested document has already been deleted. + + 422UnprocessableEntity: + description: The client request is well formed but a server validation error occured. Eg. when trying to modify or delete a read-only document (having `isReadOnly=true`). + + search200: + description: Successful operation returning array of documents matching the filtering criteria + content: + application/json: + schema: + $ref: '#/components/schemas/DocumentArray' + + search204: + description: Successful operation - no documents matching the filtering criteria + + read200: + description: The document has been succesfully found and its JSON form returned in the response content. + content: + application/json: + schema: + $ref: '#/components/schemas/Document' + headers: + 'Last-Modified': + $ref: '#/components/schemas/headerLastModified' + + history200: + description: + Changed documents since specified timestamp + content: + application/json: + schema: + $ref: '#/components/schemas/DocumentArray' + headers: + 'Last-Modified': + $ref: '#/components/schemas/headerLastModifiedMaximum' + 'ETag': + $ref: '#/components/schemas/headerEtagLastModifiedMaximum' + + history204: + description: No changes detected + + lastModified200: + description: Successful operation returning the timestamps + content: + application/json: + schema: + $ref: '#/components/schemas/LastModifiedResult' + + ###################################################################################### + schemas: + + headerLocation: + type: string + description: + Location of document - the relative part of URL. This can be used to parse the identifier + of just created document. + + Example=/api/v3/treatments/53409478-105f-11e9-ab14-d663bd873d93 + + headerLastModified: + type: string + description: + Timestamp of the last document modification on the server, formatted as + + ', :: GMT'. + + This field is relevant only for documents which were somehow modified by API v3 + (inserted, updated or deleted) and it was generated using server's clock. + + Example='Wed, 17 Oct 2018 05:13:00 GMT' + + headerLastModifiedMaximum: + type: string + description: + The latest (maximum) `srvModified` field of all returning documents, formatted as + + ', :: GMT'. + + Example='Wed, 17 Oct 2018 05:13:00 GMT' + + headerEtagLastModifiedMaximum: + type: string + description: + The latest (maximum) `srvModified` field of all returning documents. + This header does not loose milliseconds from the date (unlike the `Last-Modified` header). + + Example='W/"1525383610088"' + + paramCollection: + type: string + enum: + - devicestatus + - entries + - food + - profile + - settings + - treatments + example: 'treatments' + + paramIdentifier: + type: string + example: '53409478-105f-11e9-ab14-d663bd873d93' + + + DocumentBase: + description: Shared base for all documents + properties: + identifier: + description: + Main addressing, required field that identifies document in the collection. + + + The client should not create the identifier, the server automatically assigns it when the document is inserted. + + + The server calculates the identifier in such a way that duplicate records are automatically merged (deduplicating is made by `date`, `device` and `eventType` fields). + + + The best practise for all applications is not to loose identifiers from received documents, but save them carefully for other consumer applications/systems. + + + API v3 has a fallback mechanism in place, for documents without `identifier` field the `identifier` is set to internal `_id`, when reading or addressing these documents. + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + type: string + example: '53409478-105f-11e9-ab14-d663bd873d93' + + date: + type: integer + format: int64 + description: + Required timestamp when the record or event occured, you can choose from three input formats + + - Unix epoch in milliseconds (1525383610088) + + - Unix epoch in seconds (1525383610) + + - ISO 8601 with optional timezone ('2018-05-03T21:40:10.088Z' or '2018-05-03T23:40:10.088+02:00') + + + The date is always stored in a normalized form - UTC with zero offset. If UTC offset was present, it is going to be set in the `utcOffset` field. + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: 1525383610088 + + utcOffset: + type: integer + description: + Local UTC offset (timezone) of the event in minutes. This field can be set either directly by the client (in the incoming document) or it is automatically parsed from the `date` field. + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: 120 + + app: + type: string + description: + Application or system in which the record was entered by human or device for the first time. + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: xdrip + + device: + type: string + description: + The device from which the data originated (including serial number of the device, if it is relevant and safe). + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: 'dexcom G5' + + _id: + description: Internally assigned database id. This field is for internal server purposes only, clients communicate with API by using identifier field. + type: string + example: '58e9dfbc166d88cc18683aac' + + srvCreated: + type: integer + format: int64 + description: + The server's timestamp of document insertion into the database (Unix epoch in ms). This field appears only for documents which were inserted by API v3. + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: 1525383610088 + + subject: + type: string + description: + Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed token or JWT. + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: 'uploader' + + srvModified: + type: integer + format: int64 + description: + The server's timestamp of the last document modification in the database (Unix epoch in ms). This field appears only for documents which were somehow modified by API v3 (inserted, updated or deleted). + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: 1525383610088 + + modifiedBy: + type: string + description: + Name of the security subject (within Nightscout scope) which has patched or deleted the document for the last time. This field is automatically set by the server. + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: admin + + isValid: + type: boolean + description: + A flag set by the server only for deleted documents. This field appears + only within history operation and for documents which were deleted by API v3 (and they always have a false value) + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + example: false + + + isReadOnly: + type: boolean + description: + A flag set by client that locks the document from any changes. Every document marked with `isReadOnly=true` is forever immutable and cannot even be deleted. + + + Any attempt to modify the read-only document will end with status 422 UNPROCESSABLE ENTITY. + + + example: true + + required: + - date + - app + + + DeviceStatus: + description: State of physical device, which is a technical part of the whole T1D compensation system + allOf: + - $ref: '#/components/schemas/DocumentBase' + - type: object + properties: + some_property: + type: string + description: ... + + + Entry: + description: Blood glucose measurements and CGM calibrations + allOf: + - $ref: '#/components/schemas/DocumentBase' + - type: object + properties: + + type: + type: string + description: 'sgv, mbg, cal, etc' + + sgv: + type: number + description: The glucose reading. (only available for sgv types) + + direction: + type: string + description: Direction of glucose trend reported by CGM. (only available for sgv types) + example: '"DoubleDown", "SingleDown", "FortyFiveDown", "Flat", "FortyFiveUp", "SingleUp", "DoubleUp", "NOT COMPUTABLE", "RATE OUT OF RANGE" for xdrip' + + noise: + type: number + description: Noise level at time of reading. (only available for sgv types) + example: 'xdrip: 0, 1, 2=high, 3=high_for_predict, 4=very high, 5=extreme' + + filtered: + type: number + description: The raw filtered value directly from CGM transmitter. (only available for sgv types) + + unfiltered: + type: number + description: The raw unfiltered value directly from CGM transmitter. (only available for sgv types) + + rssi: + type: number + description: The signal strength from CGM transmitter. (only available for sgv types) + + units: + type: string + example: '"mg", "mmol"' + description: The units for the glucose value, mg/dl or mmol/l. It is strongly recommended to fill in this field. + + + Food: + description: Nutritional values of food + allOf: + - $ref: '#/components/schemas/DocumentBase' + - type: object + properties: + + food: + type: string + description: 'food, quickpick' + + category: + type: string + description: Name for a group of related records + + subcategory: + type: string + description: Name for a second level of groupping + + name: + type: string + description: Name of the food described + + portion: + type: number + description: Number of units (e.g. grams) of the whole portion described + + unit: + type: string + example: '"g", "ml", "oz"' + description: Unit for the portion + + carbs: + type: number + description: Amount of carbs in the portion in grams + + fat: + type: number + description: Amount of fat in the portion in grams + + protein: + type: number + description: Amount of proteins in the portion in grams + + energy: + type: number + description: Amount of energy in the portion in kJ + + gi: + type: number + description: 'Glycemic index (1=low, 2=medium, 3=high)' + + hideafteruse: + type: boolean + description: Flag used for quickpick + + hidden: + type: boolean + description: Flag used for quickpick + + position: + type: number + description: Ordering field for quickpick + + portions: + type: number + description: component multiplier if defined inside quickpick compound + + foods: + type: array + description: Neighbour documents (from food collection) that together make a quickpick compound + items: + $ref: '#/components/schemas/Food' + + + Profile: + description: Parameters describing body functioning relative to T1D + compensation parameters + allOf: + - $ref: '#/components/schemas/DocumentBase' + - type: object + properties: + some_property: + type: string + description: ... + + + Settings: + description: + A document representing persisted settings of some application or system (it could by Nightscout itself as well). This pack of options serves as a backup or as a shared centralized storage for multiple client instances. It is a probably good idea to `PATCH` the document instead of `UPDATE` operation, e.g. when changing one settings option in a client application. + + + `identifier` represents a client application name here, e.g. `xdrip` or `aaps`. + + + `Settings` collection has a more specific authorization required. For the `SEARCH` operation within this collection, you need an `admin` permission, such as `api:settings:admin`. The goal is to isolate individual client application settings. + + allOf: + - $ref: '#/components/schemas/DocumentBase' + - type: object + properties: + some_property: + type: string + description: ... + + + Treatment: + description: T1D compensation action + allOf: + - $ref: '#/components/schemas/DocumentBase' + - type: object + properties: + eventType: + type: string + example: '"BG Check", "Snack Bolus", "Meal Bolus", "Correction Bolus", "Carb Correction", "Combo Bolus", "Announcement", "Note", "Question", "Exercise", "Site Change", "Sensor Start", "Sensor Change", "Pump Battery Change", "Insulin Change", "Temp Basal", "Profile Switch", "D.A.D. Alert", "Temporary Target", "OpenAPS Offline", "Bolus Wizard"' + description: The type of treatment event. + + + Note: this field is immutable by the client (it cannot be updated or patched) + + + # created_at: + # type: string + # description: The date of the event, might be set retroactively. + glucose: + type: string + description: Current glucose. + glucoseType: + type: string + example: '"Sensor", "Finger", "Manual"' + description: Method used to obtain glucose, Finger or Sensor. + units: + type: string + example: '"mg/dl", "mmol/l"' + description: The units for the glucose value, mg/dl or mmol/l. It is strongly recommended to fill in this field when `glucose` is entered. + carbs: + type: number + description: Amount of carbs given. + protein: + type: number + description: Amount of protein given. + fat: + type: number + description: Amount of fat given. + insulin: + type: number + description: Amount of insulin, if any. + duration: + type: number + description: Duration in minutes. + preBolus: + type: number + description: How many minutes the bolus was given before the meal started. + splitNow: + type: number + description: Immediate part of combo bolus (in percent). + splitExt: + type: number + description: Extended part of combo bolus (in percent). + percent: + type: number + description: Eventual basal change in percent. + absolute: + type: number + description: Eventual basal change in absolute value (insulin units per hour). + targetTop: + type: number + description: Top limit of temporary target. + targetBottom: + type: number + description: Bottom limit of temporary target. + profile: + type: string + description: Name of the profile to which the pump has been switched. + reason: + type: string + description: For example the reason why the profile has been switched or why the temporary target has been set. + notes: + type: string + description: Description/notes of treatment. + enteredBy: + type: string + description: Who entered the treatment. + + + DocumentToPost: + description: Single document + type: object + oneOf: + - $ref: '#/components/schemas/DeviceStatus' + - $ref: '#/components/schemas/Entry' + - $ref: '#/components/schemas/Food' + - $ref: '#/components/schemas/Profile' + - $ref: '#/components/schemas/Settings' + - $ref: '#/components/schemas/Treatment' + example: + 'identifier': '53409478-105f-11e9-ab14-d663bd873d93' + 'date': 1532936118000 + 'utcOffset': 120 + 'carbs': 10 + 'insulin': 1 + 'eventType': 'Snack Bolus' + 'app': 'xdrip' + 'subject': 'uploader' + + + Document: + description: Single document + type: object + oneOf: + - $ref: '#/components/schemas/DeviceStatus' + - $ref: '#/components/schemas/Entry' + - $ref: '#/components/schemas/Food' + - $ref: '#/components/schemas/Profile' + - $ref: '#/components/schemas/Settings' + - $ref: '#/components/schemas/Treatment' + example: + 'identifier': '53409478-105f-11e9-ab14-d663bd873d93' + 'date': 1532936118000 + 'utcOffset': 120 + 'carbs': 10 + 'insulin': 1 + 'eventType': 'Snack Bolus' + 'srvCreated': 1532936218000 + 'srvModified': 1532936218000 + 'app': 'xdrip' + 'subject': 'uploader' + 'modifiedBy': 'admin' + + + DeviceStatusArray: + description: Array of documents + type: array + items: + $ref: '#/components/schemas/DeviceStatus' + + + EntryArray: + description: Array of documents + type: array + items: + $ref: '#/components/schemas/Entry' + + + FoodArray: + description: Array of documents + type: array + items: + $ref: '#/components/schemas/Food' + + + ProfileArray: + description: Array of documents + type: array + items: + $ref: '#/components/schemas/Profile' + + + SettingsArray: + description: Array of settings + type: array + items: + $ref: '#/components/schemas/Settings' + + + TreatmentArray: + description: Array of documents + type: array + items: + $ref: '#/components/schemas/Treatment' + + + DocumentArray: + type: object + oneOf: + - $ref: '#/components/schemas/DeviceStatusArray' + - $ref: '#/components/schemas/EntryArray' + - $ref: '#/components/schemas/FoodArray' + - $ref: '#/components/schemas/ProfileArray' + - $ref: '#/components/schemas/SettingsArray' + - $ref: '#/components/schemas/TreatmentArray' + + + Version: + description: Information about versions + type: object + properties: + + version: + description: The whole Nightscout instance version + type: string + example: '0.10.2-release-20171201' + + apiVersion: + description: API v3 subsystem version + type: string + example: '3.0.0' + + srvDate: + description: Actual server date and time in UNIX epoch format + type: number + example: 1532936118000 + + storage: + type: object + properties: + + type: + description: Type of storage engine used + type: string + example: 'mongodb' + + version: + description: Version of the storage engine + type: string + example: '4.0.6' + + + Status: + description: Information about versions and API permissions + allOf: + - $ref: '#/components/schemas/Version' + - type: object + properties: + + apiPermissions: + type: object + properties: + devicestatus: + type: string + example: 'crud' + entries: + type: string + example: 'r' + food: + type: string + example: 'crud' + profile: + type: string + example: 'r' + treatments: + type: string + example: 'crud' + + + LastModifiedResult: + description: Result of LAST MODIFIED operation + properties: + srvDate: + description: + Actual storage server date (Unix epoch in ms). + type: integer + format: int64 + example: 1556260878776 + + collections: + type: object + description: + Collections which the user have read access to. + properties: + devicestatus: + description: + Timestamp of the last modification (Unix epoch in ms), `null` when there is no timestamp found. + type: integer + format: int64 + example: 1556260760974 + treatments: + description: + Timestamp of the last modification (Unix epoch in ms), `null` when there is no timestamp found. + type: integer + format: int64 + example: 1553374184169 + entries: + description: + Timestamp of the last modification (Unix epoch in ms), `null` when there is no timestamp found. + type: integer + format: int64 + example: 1556260758768 + profile: + description: + Timestamp of the last modification (Unix epoch in ms), `null` when there is no timestamp found. + type: integer + format: int64 + example: 1548524042744 + + ###################################################################################### + securitySchemes: + + accessToken: + type: apiKey + name: token + in: query + description: >- + Add token as query item in the URL or as HTTP header. You can manage access token in + `/admin`. + + Each operation requires a specific permission that has to be granted (via security role) to the security subject, which was authenticated by `token` parameter/header or `JWT`. E.g. for creating new `devicestatus` document via API you need `api:devicestatus:create` permission. + + jwtoken: + type: http + scheme: bearer + description: Use this if you know the temporary json webtoken. + bearerFormat: JWT \ No newline at end of file diff --git a/lib/authorization/index.js b/lib/authorization/index.js index 0c0c6f5f2a7..feaed739b42 100644 --- a/lib/authorization/index.js +++ b/lib/authorization/index.js @@ -55,6 +55,8 @@ function init (env, ctx) { return token; } + authorization.extractToken = extractToken; + function authorizeAccessToken (req) { var accessToken = req.query.token; @@ -139,22 +141,25 @@ function init (env, ctx) { authorization.resolve = function resolve (data, callback) { + var defaultShiros = storage.rolesToShiros(defaultRoles); + + if (storage.doesAccessTokenExist(data.api_secret)) { + authorization.resolveAccessToken (data.api_secret, callback, defaultShiros); + return; + } + if (authorizeAdminSecret(data.api_secret)) { var admin = shiroTrie.new(); admin.add(['*']); return callback(null, { shiros: [ admin ] }); } - var defaultShiros = storage.rolesToShiros(defaultRoles); - if (data.token) { jwt.verify(data.token, env.api_secret, function result(err, verified) { if (err) { return callback(err, { shiros: [ ] }); } else { - var resolved = storage.resolveSubjectAndPermissions(verified.accessToken); - var shiros = resolved.shiros.concat(defaultShiros); - return callback(null, { shiros: shiros, subject: resolved.subject }); + authorization.resolveAccessToken (verified.accessToken, callback, defaultShiros); } }); } else { @@ -163,6 +168,21 @@ function init (env, ctx) { }; + authorization.resolveAccessToken = function resolveAccessToken (accessToken, callback, defaultShiros) { + + if (!defaultShiros) { + defaultShiros = storage.rolesToShiros(defaultRoles); + } + + let resolved = storage.resolveSubjectAndPermissions(accessToken); + if (!resolved || !resolved.subject) { + return callback('Subject not found', null); + } + + let shiros = resolved.shiros.concat(defaultShiros); + return callback(null, { shiros: shiros, subject: resolved.subject }); + }; + authorization.isPermitted = function isPermitted (permission, opts) { @@ -177,6 +197,25 @@ function init (env, ctx) { var remoteIP = getRemoteIP(req); + var secret = adminSecretFromRequest(req); + var defaultShiros = storage.rolesToShiros(defaultRoles); + + if (storage.doesAccessTokenExist(secret)) { + var resolved = storage.resolveSubjectAndPermissions (secret); + + if (authorization.checkMultiple(permission, resolved.shiros)) { + console.log(LOG_GRANTED, remoteIP, resolved.accessToken , permission); + next(); + } else if (authorization.checkMultiple(permission, defaultShiros)) { + console.log(LOG_GRANTED, remoteIP, resolved.accessToken, permission, 'default'); + next( ); + } else { + console.log(LOG_DENIED, remoteIP, resolved.accessToken, permission); + res.sendJSONStatus(res, consts.HTTP_UNAUTHORIZED, 'Unauthorized', 'Invalid/Missing'); + } + return; + } + if (authorizeAdminSecretWithRequest(req)) { console.log(LOG_GRANTED, remoteIP, 'api-secret', permission); next( ); @@ -184,7 +223,6 @@ function init (env, ctx) { } var token = extractToken(req); - var defaultShiros = storage.rolesToShiros(defaultRoles); if (token) { jwt.verify(token, env.api_secret, function result(err, verified) { diff --git a/lib/authorization/storage.js b/lib/authorization/storage.js index 41969be150f..3a4c4490876 100644 --- a/lib/authorization/storage.js +++ b/lib/authorization/storage.js @@ -28,6 +28,11 @@ function init (env, ctx) { obj.created_at = (new Date()).toISOString(); } collection.insert(obj, function (err, doc) { + if (err != null && err.message) { + console.log('Data insertion error', err.message); + fn(err.message, null); + return; + } storage.reload(function loaded() { fn(null, doc.ops); }); @@ -117,6 +122,12 @@ function init (env, ctx) { , { name: 'activity', permissions: [ 'api:activity:create' ] } ]; + storage.getSHA1 = function getSHA1 (message) { + var shasum = crypto.createHash('sha1'); + shasum.update(message); + return shasum.digest('hex'); + } + storage.reload = function reload (callback) { storage.listRoles({sort: {name: 1}}, function listResults (err, results) { @@ -147,6 +158,7 @@ function init (env, ctx) { var abbrev = subject.name.toLowerCase().replace(/[\W]/g, '').substring(0, 10); subject.digest = shasum.digest('hex'); subject.accessToken = abbrev + '-' + subject.digest.substring(0, 16); + subject.accessTokenDigest = storage.getSHA1(subject.accessToken); } return subject; @@ -195,17 +207,28 @@ function init (env, ctx) { }; storage.findSubject = function findSubject (accessToken) { - var prefix = _.last(accessToken.split('-')); + + if (!accessToken) return null; + + var split_token = accessToken.split('-'); + var prefix = split_token ? _.last(split_token) : ''; if (prefix.length < 16) { return null; } return _.find(storage.subjects, function matches (subject) { - return subject.digest.indexOf(prefix) === 0; + return subject.accessTokenDigest.indexOf(accessToken) === 0 || subject.digest.indexOf(prefix) === 0; }); }; + storage.doesAccessTokenExist = function doesAccessTokenExist(accessToken) { + if (storage.findSubject(accessToken)) { + return true; + } + return false; + } + storage.resolveSubjectAndPermissions = function resolveSubjectAndPermissions (accessToken) { var shiros = []; diff --git a/lib/client/boluscalc.js b/lib/client/boluscalc.js index 5c83f478f57..ae019ebdfef 100644 --- a/lib/client/boluscalc.js +++ b/lib/client/boluscalc.js @@ -5,8 +5,8 @@ var moment = require('moment-timezone'); var times = require('../times'); var Storages = require('js-storage'); -function init(client, $) { - var boluscalc = { }; +function init (client, $) { + var boluscalc = {}; var translate = client.translate; var storage = Storages.localStorage; @@ -34,16 +34,15 @@ function init(client, $) { } } - function isProfileEnabled(profiles) { - return client.settings.enable.indexOf('profile') > -1 - && client.settings.extendedSettings.profile - && client.settings.extendedSettings.profile.multiple - && profiles.length > 1; + function isProfileEnabled (profiles) { + return client.settings.enable.indexOf('profile') > -1 && + client.settings.extendedSettings.profile && + client.settings.extendedSettings.profile.multiple && + profiles.length > 1; } - function isTouch() { - try { document.createEvent('TouchEvent'); return true; } - catch (e) { return false; } + function isTouch () { + try { document.createEvent('TouchEvent'); return true; } catch (e) { return false; } } function setDateAndTime (time) { @@ -52,11 +51,11 @@ function init(client, $) { eventDate.val(time.format('YYYY-MM-DD')); } - function mergeDateAndTime ( ) { + function mergeDateAndTime () { return client.utils.mergeInputTime(eventTime.val(), eventDate.val()); } - function updateTime(ele, time) { + function updateTime (ele, time) { ele.attr('oldminutes', time.minutes()); ele.attr('oldhours', time.hours()); } @@ -98,15 +97,15 @@ function init(client, $) { } }; - boluscalc.dateTimeFocus = function dateTimeFocus(event) { + boluscalc.dateTimeFocus = function dateTimeFocus (event) { $('#bc_othertime').prop('checked', true); updateTime($(this), mergeDateAndTime()); maybePrevent(event); }; - boluscalc.dateTimeChange = function dateTimeChange(event) { + boluscalc.dateTimeChange = function dateTimeChange (event) { $('#bc_othertime').prop('checked', true); -// client.utils.setYAxisOffset(50); //50% of extend + // client.utils.setYAxisOffset(50); //50% of extend var ele = $(this); var merged = mergeDateAndTime(); @@ -126,29 +125,29 @@ function init(client, $) { boluscalc.calculateInsulin(); maybePrevent(event); -// Nightscout.utils.updateBrushToTime(moment.toDate()); + // Nightscout.utils.updateBrushToTime(moment.toDate()); }; - boluscalc.eventTimeTypeChange = function eventTimeTypeChange(event) { + boluscalc.eventTimeTypeChange = function eventTimeTypeChange (event) { if ($('#bc_othertime').is(':checked')) { $('#bc_eventTimeValue').focus(); - $('#bc_retro').css('display',''); - if (mergeDateAndTime()moment()) { - $('#bc_retro').css('background-color','blue').text(translate('IN THE FUTURE')); + $('#bc_retro').css('display', ''); + if (mergeDateAndTime() < moment()) { + $('#bc_retro').css('background-color', 'red').text(translate('RETRO MODE')); + } else if (mergeDateAndTime() > moment()) { + $('#bc_retro').css('background-color', 'blue').text(translate('IN THE FUTURE')); } else { - $('#bc_retro').css('display','none'); + $('#bc_retro').css('display', 'none'); } } else { - $('#bc_retro').css('display','none'); + $('#bc_retro').css('display', 'none'); setDateAndTime(); boluscalc.updateVisualisations(client.sbx); - if (event) { - boluscalc.calculateInsulin(); - } -// Nightscout.utils.setYAxisOffset(50); //50% of extend -// Nightscout.utils.updateBrushToTime(Nightscout.utils.mergeInputTime($('#bc_eventTimeValue').val(), $('#bc_eventDateValue').val()).toDate()); + if (event) { + boluscalc.calculateInsulin(); + } + // Nightscout.utils.setYAxisOffset(50); //50% of extend + // Nightscout.utils.updateBrushToTime(Nightscout.utils.mergeInputTime($('#bc_eventTimeValue').val(), $('#bc_eventDateValue').val()).toDate()); } maybePrevent(event); }; @@ -159,20 +158,20 @@ function init(client, $) { maybePrevent(event); }; - boluscalc.prepare = function prepare( ) { + boluscalc.prepare = function prepare () { foods = []; $('#bc_profile').empty(); var profiles = client.profilefunctions.listBasalProfiles(); - profiles.forEach(function (p) { + profiles.forEach(function(p) { $('#bc_profile').append(''); }); $('#bc_profileLabel').toggle(isProfileEnabled(profiles)); - $('#bc_usebg').prop('checked','checked'); - $('#bc_usecarbs').prop('checked','checked'); - $('#bc_usecob').prop('checked',''); - $('#bc_useiob').prop('checked','checked'); - $('#bc_bgfromsensor').prop('checked','checked'); + $('#bc_usebg').prop('checked', 'checked'); + $('#bc_usecarbs').prop('checked', 'checked'); + $('#bc_usecob').prop('checked', ''); + $('#bc_useiob').prop('checked', 'checked'); + $('#bc_bgfromsensor').prop('checked', 'checked'); $('#bc_carbs').val(''); $('#bc_quickpick').val(-1); $('#bc_preBolus').val(0); @@ -189,9 +188,9 @@ function init(client, $) { boluscalc.calculateInsulin = function calculateInsulin (event) { maybePrevent(event); - boluscalc.gatherBoluscalcData( ); + boluscalc.gatherBoluscalcData(); boluscalc.updateGui(boluscalc.record); - return boluscalc.record; + return boluscalc.record; }; boluscalc.updateGui = function updateGui (record) { @@ -236,11 +235,11 @@ function init(client, $) { $('#bc_bg').css('background-color', ''); } $('#bc_inzulinbg').text(record.insulinbg.toFixed(2)); - $('#bc_inzulinbg').attr('title', - 'Target BG range: '+targetBGLow + ' - ' + targetBGHigh + - '\nISF: ' + isf + - '\nBG diff: ' + record.bgdiff.toFixed(1) - ); + $('#bc_inzulinbg').attr('title' + , 'Target BG range: ' + targetBGLow + ' - ' + targetBGHigh + + '\nISF: ' + isf + + '\nBG diff: ' + record.bgdiff.toFixed(1) + ); } else { $('#bc_inzulinbgtd').css('background-color', ''); $('#bc_bg').css('background-color', ''); @@ -252,51 +251,51 @@ function init(client, $) { if (record.foods.length) { var html = ''; var carbs = 0; - for (var fi=0; fi'; + html += ''; } html += ''; - html += ''; - html += ''; - html += ''; + html += ''; + html += ''; + html += ''; html += ''; } html += '
'+ f.name + ''+ (f.portion*f.portions).toFixed(1) + ' ' + translate(f.unit) + '('+ (f.carbs*f.portions).toFixed(1) + ' g)' + f.name + '' + (f.portion * f.portions).toFixed(1) + ' ' + translate(f.unit) + '(' + (f.carbs * f.portions).toFixed(1) + ' g)
'; $('#bc_food').html(html); $('.deleteFoodRecord').click(deleteFoodRecord); $('#bc_carbs').val(carbs.toFixed(0)); - $('#bc_carbs').attr('disabled',true); - $('#bc_gi').css('display','none'); - $('#bc_gicalculated').css('display',''); + $('#bc_carbs').attr('disabled', true); + $('#bc_gi').css('display', 'none'); + $('#bc_gicalculated').css('display', ''); $('#bc_gicalculated').text(record.gi); } else { $('#bc_food').html(''); - $('#bc_carbs').attr('disabled',false); - $('#bc_gi').css('display',''); - $('#bc_gicalculated').css('display','none'); + $('#bc_carbs').attr('disabled', false); + $('#bc_gi').css('display', ''); + $('#bc_gicalculated').css('display', 'none'); $('#bc_gicalculated').text(''); } // Show Carbs if ($('#bc_usecarbs').is(':checked')) { if ($('#bc_carbs').val() === '') { - $('#bc_carbs').css('background-color',''); - } else if (isNaN(parseInt($('#bc_carbs').val().replace(',','.')))) { - $('#bc_carbs').css('background-color','red'); + $('#bc_carbs').css('background-color', ''); + } else if (isNaN(parseInt($('#bc_carbs').val().replace(',', '.')))) { + $('#bc_carbs').css('background-color', 'red'); } else { - $('#bc_carbs').css('background-color',''); + $('#bc_carbs').css('background-color', ''); } $('#bc_inzulincarbs').text(record.insulincarbs.toFixed(2)); - $('#bc_inzulincarbs').attr('title','IC: ' + ic); + $('#bc_inzulincarbs').attr('title', 'IC: ' + ic); } else { - $('#bc_carbs').css('background-color',''); + $('#bc_carbs').css('background-color', ''); $('#bc_inzulincarbs').text(''); - $('#bc_inzulincarbs').attr('title',''); + $('#bc_inzulincarbs').attr('title', ''); $('#bc_carbs').text(''); } @@ -309,21 +308,21 @@ function init(client, $) { if (record.othercorrection === 0 && record.carbs === 0 && record.cob === 0 && record.bg > 0 && outcome > targetBGLow && outcome < targetBGHigh) { $('#bc_carbsneeded').text(''); $('#bc_insulinover').text(''); - $('#bc_carbsneededtr').css('display','none'); - $('#bc_insulinneededtr').css('display','none'); - $('#bc_calculationintarget').css('display',''); - } else if (record.insulin<0) { - $('#bc_carbsneeded').text(record.carbsneeded+' g'); + $('#bc_carbsneededtr').css('display', 'none'); + $('#bc_insulinneededtr').css('display', 'none'); + $('#bc_calculationintarget').css('display', ''); + } else if (record.insulin < 0) { + $('#bc_carbsneeded').text(record.carbsneeded + ' g'); $('#bc_insulinover').text(record.insulin.toFixed(2)); - $('#bc_carbsneededtr').css('display',''); - $('#bc_insulinneededtr').css('display','none'); - $('#bc_calculationintarget').css('display','none'); + $('#bc_carbsneededtr').css('display', ''); + $('#bc_insulinneededtr').css('display', 'none'); + $('#bc_calculationintarget').css('display', 'none'); } else { $('#bc_carbsneeded').text(''); $('#bc_insulinover').text(''); - $('#bc_carbsneededtr').css('display','none'); - $('#bc_insulinneededtr').css('display',''); - $('#bc_calculationintarget').css('display','none'); + $('#bc_carbsneededtr').css('display', 'none'); + $('#bc_insulinneededtr').css('display', ''); + $('#bc_calculationintarget').css('display', 'none'); } // Show basal rate @@ -335,7 +334,7 @@ function init(client, $) { $('#bc_basal').text(tempMark + basal.totalbasal.toFixed(3)); }; - boluscalc.gatherBoluscalcData = function gatherBoluscalcData() { + boluscalc.gatherBoluscalcData = function gatherBoluscalcData () { boluscalc.record = {}; var record = boluscalc.record; @@ -351,7 +350,6 @@ function init(client, $) { return; } - // Calculate event time from date & time record.eventTime = new Date(); if ($('#bc_othertime').is(':checked')) { @@ -373,19 +371,19 @@ function init(client, $) { record.ic = ic; if (targetBGLow === 0 || targetBGHigh === 0 || isf === 0 || ic === 0) { - $('#bc_inzulinbgtd').css('background-color','red'); + $('#bc_inzulinbgtd').css('background-color', 'red'); boluscalc.record = {}; return; } else { - $('#bc_inzulinbgtd').css('background-color',''); + $('#bc_inzulinbgtd').css('background-color', ''); } if (ic === 0) { - $('#bc_inzulincarbstd').css('background-color','red'); + $('#bc_inzulincarbstd').css('background-color', 'red'); boluscalc.record = {}; return; } else { - $('#bc_inzulincarbstd').css('background-color',''); + $('#bc_inzulincarbstd').css('background-color', ''); } // Load IOB @@ -407,7 +405,7 @@ function init(client, $) { record.insulinbg = 0; record.bgdiff = 0; if ($('#bc_usebg').is(':checked')) { - record.bg = parseFloat($('#bc_bg').val().replace(',','.')); + record.bg = parseFloat($('#bc_bg').val().replace(',', '.')); if (isNaN(record.bg)) { record.bg = 0; } @@ -417,7 +415,7 @@ function init(client, $) { record.bgdiff = record.bg - targetBGHigh; } record.bgdiff = roundTo(record.bgdiff, 0.1); - if (record.bg !== 0){ + if (record.bg !== 0) { record.insulinbg = roundTo(record.bgdiff / isf, 0.01); } } @@ -427,7 +425,7 @@ function init(client, $) { record.foods = _.cloneDeep(foods); if (record.foods.length) { var gisum = 0; - for (var fi=0; fi= 0) { @@ -613,16 +612,16 @@ function init(client, $) { var foodlist = []; var databaseloaded = false; var filter = { - category: '' + category: '' , subcategory: '' , name: '' }; - boluscalc.loadFoodDatabase = function loadFoodDatabase(event, callback) { + boluscalc.loadFoodDatabase = function loadFoodDatabase (event, callback) { categories = []; foodlist = []; var records = client.sbx.data.food || []; - records.forEach(function (r) { + records.forEach(function(r) { if (r.type == 'food') { foodlist.push(r); if (r.category && !categories[r.category]) { @@ -640,77 +639,77 @@ function init(client, $) { if (callback) { callback(); } }; - boluscalc.loadFoodQuickpicks = function loadFoodQuickpicks( ) { + boluscalc.loadFoodQuickpicks = function loadFoodQuickpicks () { // Load quickpicks quickpicks = []; var records = client.sbx.data.food || []; - records.forEach(function (r) { + records.forEach(function(r) { if (r.type == 'quickpick') { quickpicks.push(r); } }); $('#bc_quickpick').empty().append(''); - for (var i=0; i' + r.name + ' (' + r.carbs + ' g)'); - }; + $('#bc_quickpick').append(''); + } $('#bc_quickpick').val(-1); $('#bc_quickpick').change(quickpickChange); }; - function fillForm(event) { + function fillForm (event) { $('#bc_filter_category').empty().append(''); - Object.keys(categories).forEach( function eachCategory(s) { - $('#bc_filter_category').append(''); + Object.keys(categories).forEach(function eachCategory (s) { + $('#bc_filter_category').append(''); }); filter.category = ''; fillSubcategories(); $('#bc_filter_category').change(fillSubcategories); $('#bc_filter_subcategory').change(doFilter); - $('#bc_filter_name').on('input',doFilter); + $('#bc_filter_name').on('input', doFilter); maybePrevent(event); return false; } - function fillSubcategories(event) { + function fillSubcategories (event) { maybePrevent(event); filter.category = $('#bc_filter_category').val(); filter.subcategory = ''; $('#bc_filter_subcategory').empty().append(''); if (filter.category !== '') { - Object.keys(categories[filter.category]).forEach( function eachSubcategory(s) { - $('#bc_filter_subcategory').append(''); + Object.keys(categories[filter.category]).forEach(function eachSubcategory (s) { + $('#bc_filter_subcategory').append(''); }); } doFilter(); } - function doFilter(event) { + function doFilter (event) { if (event) { filter.category = $('#bc_filter_category').val(); filter.subcategory = $('#bc_filter_subcategory').val(); filter.name = $('#bc_filter_name').val(); } $('#bc_data').empty(); - for (var i=0; i' + o + ''); + o += 'Carbs: ' + foodlist[i].carbs + ' g'; + $('#bc_data').append(''); } $('#bc_addportions').val('1'); maybePrevent(event); } - function addFoodFromDatabase(event) { + function addFoodFromDatabase (event) { if (!databaseloaded) { boluscalc.loadFoodDatabase(event, addFoodFromDatabase); return; @@ -718,30 +717,32 @@ function init(client, $) { $('#bc_addportions').val('1'); $('#bc_addfooddialog').dialog({ - width: 640 + width: 640 , height: 400 - , buttons: [ - { text: translate('Add'), - click: function() { - var index = $('#bc_data').val(); - var portions = parseFloat($('#bc_addportions').val().replace(',','.')); - if (index !== null && !isNaN(portions) && portions >0) { - foodlist[index].portions = portions; - foods.push(_.cloneDeep(foodlist[index])); - $( this ).dialog( 'close' ); - boluscalc.calculateInsulin(); + , buttons: [ + { + text: translate('Add') + , click: function() { + var index = $('#bc_data').val(); + var portions = parseFloat($('#bc_addportions').val().replace(',', '.')); + if (index !== null && !isNaN(portions) && portions > 0) { + foodlist[index].portions = portions; + foods.push(_.cloneDeep(foodlist[index])); + $(this).dialog('close'); + boluscalc.calculateInsulin(); + } } - } - }, - { text: translate('Reload database'), - class: 'leftButton', - click: boluscalc.loadFoodDatabase + } + , { + text: translate('Reload database') + , class: 'leftButton' + , click: boluscalc.loadFoodDatabase } ] - , open : function() { + , open: function() { $(this).parent().css('box-shadow', '20px 20px 20px 0px black'); - $(this).parent().find('.ui-dialog-buttonset' ).css({'width':'100%','text-align':'right'}); - $(this).parent().find('button:contains("'+translate('Add')+'")').css({'float':'left'}); + $(this).parent().find('.ui-dialog-buttonset').css({ 'width': '100%', 'text-align': 'right' }); + $(this).parent().find('button:contains("' + translate('Add') + '")').css({ 'float': 'left' }); $('#bc_filter_name').focus(); } @@ -750,7 +751,7 @@ function init(client, $) { return false; } - function findClosestSGVToPastTime(time) { + function findClosestSGVToPastTime (time) { var nowData = client.entries.filter(function(d) { return d.type === 'sgv' && d.mills <= time.getTime(); }); @@ -766,12 +767,12 @@ function init(client, $) { // Make it faster on mobile devices $('.insulincalculationpart').change(boluscalc.calculateInsulin); } else { - $('.insulincalculationpart').on('input',boluscalc.calculateInsulin); + $('.insulincalculationpart').on('input', boluscalc.calculateInsulin); $('input:checkbox.insulincalculationpart').change(boluscalc.calculateInsulin); } $('#bc_bgfrommeter').change(boluscalc.calculateInsulin); $('#bc_addfromdatabase').click(addFoodFromDatabase); - $('#bc_bgfromsensor').change(function bc_bgfromsensor_click(event) { + $('#bc_bgfromsensor').change(function bc_bgfromsensor_click (event) { boluscalc.updateVisualisations(client.sbx); boluscalc.calculateInsulin(); maybePrevent(event); diff --git a/lib/client/browser-settings.js b/lib/client/browser-settings.js index bfac4df8067..2119b39da40 100644 --- a/lib/client/browser-settings.js +++ b/lib/client/browser-settings.js @@ -8,18 +8,18 @@ var Storages = require('js-storage'); function init (client, serverSettings, $) { - serverSettings = serverSettings || {settings: {}}; + serverSettings = serverSettings || { settings: {} }; var storage = Storages.localStorage; var settings = require('../settings')(); - function loadForm ( ) { + function loadForm () { var utils = client.utils; var language = require('../language')(); language.set(settings.language); var translate = language.translate; - function appendThresholdValue(threshold) { + function appendThresholdValue (threshold) { return settings.alarmTypes.indexOf('simple') === -1 ? '' : ' (' + utils.scaleMgdl(threshold) + ')'; } @@ -61,8 +61,8 @@ function init (client, serverSettings, $) { var langSelect = $('#language'); - _.each(language.languages, function eachLanguage(lang) { - langSelect.append(''); + _.each(language.languages, function eachLanguage (lang) { + langSelect.append(''); }); langSelect.val(settings.language); @@ -79,7 +79,10 @@ function init (client, serverSettings, $) { var showPluginsSettings = $('#show-plugins'); var hasPluginsToShow = false; - client.plugins.eachEnabledPlugin(function each(plugin) { + + const pluginPrefs = []; + + client.plugins.eachEnabledPlugin(function each (plugin) { if (client.plugins.specialPlugins.indexOf(plugin.name) > -1) { //ignore these, they are always on for now } else { @@ -89,15 +92,70 @@ function init (client, serverSettings, $) { dd.find('input').prop('checked', settings.showPlugins.indexOf(plugin.name) > -1); hasPluginsToShow = true; } + + if (plugin.getClientPrefs) { + const prefs = plugin.getClientPrefs(); + pluginPrefs.push({ + plugin + , prefs + }) + } }); showPluginsSettings.toggle(hasPluginsToShow); + const bs = $('#browserSettings'); + const toggleCheckboxes = []; + + if (pluginPrefs.length > 0) { + pluginPrefs.forEach(function(e) { + // Only show settings if plugin is visible + if (settings.showPlugins.indexOf(e.plugin.name) > -1) { + const label = e.plugin.label; + const dl = $('
'); + dl.append(`
${label}
`); + e.prefs.forEach(function(p) { + const id = e.plugin.name + "-" + p.id; + const label = p.label; + if (p.type == 'boolean') { + const html = $(`
`); + dl.append(html); + if (storage.get(id) == true) { + toggleCheckboxes.push(id); + } + } + }); + bs.append(dl); + } + }); + } + + toggleCheckboxes.forEach(function(cb) { + $('#' + cb).prop('checked', true); + }); + $('#editprofilelink').toggle(settings.isEnabled('iob') || settings.isEnabled('cob') || settings.isEnabled('bwp') || settings.isEnabled('basal')); + + //fetches token from url + var parts = (location.search || '?').substring(1).split('&'); + var token = ''; + parts.forEach(function (val) { + if (val.startsWith('token=')) { + token = val.substring('token='.length); + } + }); + //if there is a token, append it to each of the links in the hamburger menu + if (token != '') { + token = '?token=' + token; + $('#reportlink').attr('href', 'report' + token); + $('#editprofilelink').attr('href', 'profile' + token); + $('#admintoolslink').attr('href', 'admin' + token); + $('#editfoodlink').attr('href', 'food' + token); + } } - function wireForm ( ) { + function wireForm () { $('#useDefaults').click(function(event) { settings.eachSetting(function clearEachSetting (name) { storage.remove(name); @@ -108,43 +166,55 @@ function init (client, serverSettings, $) { }); $('#save').click(function(event) { - function checkedPluginNames() { + function checkedPluginNames () { var checkedPlugins = []; - $('#show-plugins input:checked').each(function eachPluginCheckbox(index, checkbox) { + $('#show-plugins input:checked').each(function eachPluginCheckbox (index, checkbox) { checkedPlugins.push($(checkbox).val()); }); return checkedPlugins.join(' '); } - function storeInBrowser(data) { - for (var k in data) { - if (data.hasOwnProperty(k)) { - storage.set(k, data[k]); - } + client.plugins.eachEnabledPlugin(function each (plugin) { + if (plugin.getClientPrefs) { + const prefs = plugin.getClientPrefs(); + + prefs.forEach(function(p) { + const id = plugin.name + "-" + p.id; + if (p.type == 'boolean') { + const val = $("#" + id).prop('checked'); + storage.set(id, val); + } + }); } + }); + + function storeInBrowser (data) { + Object.keys(data).forEach(k => { + storage.set(k, data[k]); + }); } storeInBrowser({ - units: $('input:radio[name=units-browser]:checked').val(), - alarmUrgentHigh: $('#alarm-urgenthigh-browser').prop('checked'), - alarmHigh: $('#alarm-high-browser').prop('checked'), - alarmLow: $('#alarm-low-browser').prop('checked'), - alarmUrgentLow: $('#alarm-urgentlow-browser').prop('checked'), - alarmTimeagoWarn: $('#alarm-timeagowarn-browser').prop('checked'), - alarmTimeagoWarnMins: parseInt($('#alarm-timeagowarnmins-browser').val()) || 15, - alarmTimeagoUrgent: $('#alarm-timeagourgent-browser').prop('checked'), - alarmTimeagoUrgentMins: parseInt($('#alarm-timeagourgentmins-browser').val()) || 30, - nightMode: $('#nightmode-browser').prop('checked'), - editMode: $('#editmode-browser').prop('checked'), - showRawbg: $('input:radio[name=show-rawbg]:checked').val(), - customTitle: $('input#customTitle').prop('value'), - theme: $('input:radio[name=theme-browser]:checked').val(), - timeFormat: parseInt($('input:radio[name=timeformat-browser]:checked').val()), - language: $('#language').val(), - scaleY: $('#scaleY').val(), - basalrender: $('#basalrender').val(), - showPlugins: checkedPluginNames(), - storageVersion: STORAGE_VERSION + units: $('input:radio[name=units-browser]:checked').val() + , alarmUrgentHigh: $('#alarm-urgenthigh-browser').prop('checked') + , alarmHigh: $('#alarm-high-browser').prop('checked') + , alarmLow: $('#alarm-low-browser').prop('checked') + , alarmUrgentLow: $('#alarm-urgentlow-browser').prop('checked') + , alarmTimeagoWarn: $('#alarm-timeagowarn-browser').prop('checked') + , alarmTimeagoWarnMins: parseInt($('#alarm-timeagowarnmins-browser').val()) || 15 + , alarmTimeagoUrgent: $('#alarm-timeagourgent-browser').prop('checked') + , alarmTimeagoUrgentMins: parseInt($('#alarm-timeagourgentmins-browser').val()) || 30 + , nightMode: $('#nightmode-browser').prop('checked') + , editMode: $('#editmode-browser').prop('checked') + , showRawbg: $('input:radio[name=show-rawbg]:checked').val() + , customTitle: $('input#customTitle').prop('value') + , theme: $('input:radio[name=theme-browser]:checked').val() + , timeFormat: parseInt($('input:radio[name=timeformat-browser]:checked').val()) + , language: $('#language').val() + , scaleY: $('#scaleY').val() + , basalrender: $('#basalrender').val() + , showPlugins: checkedPluginNames() + , storageVersion: STORAGE_VERSION }); event.preventDefault(); @@ -152,13 +222,13 @@ function init (client, serverSettings, $) { }); } - function showLocalstorageError ( ) { + function showLocalstorageError () { var msg = 'Settings are disabled.

Please enable cookies so you may customize your Nightscout site.'; - $('.browserSettings').html('Settings'+msg+''); + $('.browserSettings').html('Settings' + msg + ''); $('#save').hide(); } - function handleStorageVersions ( ) { + function handleStorageVersions () { var previousVersion = parseInt(storage.get('storageVersion')); //un-versioned settings @@ -174,7 +244,7 @@ function init (client, serverSettings, $) { } } - settings.extendedSettings = serverSettings.extendedSettings || {settings: {}}; + settings.extendedSettings = serverSettings.extendedSettings || { settings: {} }; try { settings.eachSetting(function setEach (name) { @@ -200,18 +270,42 @@ function init (client, serverSettings, $) { var stored = storage.get('basalrender'); settings.extendedSettings.basal.render = stored !== null ? stored : settings.extendedSettings.basal.render; - } catch(err) { + + } catch (err) { console.error(err); showLocalstorageError(); } - init.loadAndWireForm = function loadAndWireForm ( ) { + init.loadAndWireForm = function loadAndWireForm () { loadForm(); wireForm(); }; + init.loadPluginSettings = function loadPluginSettings (client) { + + client.plugins.eachEnabledPlugin(function each (plugin) { + if (plugin.getClientPrefs) { + const prefs = plugin.getClientPrefs(); + + if (!settings.extendedSettings[plugin.name]) { + settings.extendedSettings[plugin.name] = {}; + } + + const settingsBase = settings.extendedSettings[plugin.name]; + + prefs.forEach(function(p) { + const id = plugin.name + "-" + p.id; + const stored = storage.get(id); + if (stored !== null) { + settingsBase[p.id] = stored; + } + }); + } + }); + + } + return settings; } - module.exports = init; diff --git a/lib/client/browser-utils.js b/lib/client/browser-utils.js index fc9a983d0e0..4f920588f80 100644 --- a/lib/client/browser-utils.js +++ b/lib/client/browser-utils.js @@ -13,9 +13,9 @@ function init ($) { $('#drawer').find('.tip').tooltip(); } $.fn.tooltip.defaults = { - fade: true, - gravity: 'n', - opacity: 0.75 + fade: true + , gravity: 'n' + , opacity: 0.75 }; var querystring = queryParms(); @@ -38,31 +38,30 @@ function init ($) { event.preventDefault(); }); - $('.navigation a').click(function navigationClick ( ) { + $('.navigation a').click(function navigationClick () { closeDrawer('#drawer'); }); - function reload() { + function reload () { //strip '#' so form submission does not fail var url = window.location.href; url = url.replace(/#$/, ''); window.location.href = url; } - - function queryParms() { + function queryParms () { var params = {}; if (location.search) { location.search.substr(1).split('&').forEach(function(item) { + // eslint-disable-next-line no-useless-escape params[item.split('=')[0]] = item.split('=')[1].replace(/[_\+]/g, ' '); }); } return params; } - function isTouch() { - try { document.createEvent('TouchEvent'); return true; } - catch (e) { return false; } + function isTouch () { + try { document.createEvent('TouchEvent'); return true; } catch (e) { return false; } } function closeLastOpenedDrawer (callback) { @@ -73,15 +72,15 @@ function init ($) { } } - function closeDrawer(id, callback) { + function closeDrawer (id, callback) { lastOpenedDrawer = null; $('html, body').css({ scrollTop: 0 }); - $(id).css({display: 'none', right: '-300px'}); + $(id).css({ display: 'none', right: '-300px' }); if (callback) { callback(); } } - function openDrawer(id, prepare) { - function closeOpenDraw(callback) { + function openDrawer (id, prepare) { + function closeOpenDraw (callback) { if (lastOpenedDrawer) { closeDrawer(lastOpenedDrawer, callback); } else { @@ -89,11 +88,11 @@ function init ($) { } } - closeOpenDraw(function () { + closeOpenDraw(function() { lastOpenedDrawer = id; if (prepare) { prepare(); } - var style = {display:'block', right: '0'}; + var style = { display: 'block', right: '0' }; var windowWidth = $(window).width(); var windowHeight = $(window).height(); @@ -114,13 +113,12 @@ function init ($) { style.width = '350px'; } - $(id).css(style); }); } - function toggleDrawer(id, openPrepare, closeCallback) { + function toggleDrawer (id, openPrepare, closeCallback) { if (lastOpenedDrawer === id) { closeDrawer(id, closeCallback); } else { @@ -128,13 +126,13 @@ function init ($) { } } - function closeNotification() { + function closeNotification () { var notify = $('#notification'); notify.hide(); notify.find('span').html(''); } - function showNotification(note, type) { + function showNotification (note, type) { var notify = $('#notification'); notify.hide(); @@ -150,10 +148,10 @@ function init ($) { notify.show(); } - function getLastOpenedDrawer() { + function getLastOpenedDrawer () { return lastOpenedDrawer; } - + return { reload: reload , queryParms: queryParms diff --git a/lib/client/careportal.js b/lib/client/careportal.js index f6c73c2abb3..a5c86232d8e 100644 --- a/lib/client/careportal.js +++ b/lib/client/careportal.js @@ -4,21 +4,16 @@ var moment = require('moment-timezone'); var _ = require('lodash'); var parse_duration = require('parse-duration'); // https://www.npmjs.com/package/parse-duration var times = require('../times'); +var consts = require('../constants'); var Storages = require('js-storage'); function init (client, $) { - var careportal = { }; + var careportal = {}; var translate = client.translate; var storage = Storages.localStorage; var units = client.settings.units; - careportal.allEventTypes = client.plugins.getAllEventTypes(client.sbx); - - careportal.events = _.map(careportal.allEventTypes, function each (event) { - return _.pick(event, ['val', 'name']); - }); - var eventTime = $('#eventTimeValue'); var eventDate = $('#eventDateValue'); @@ -28,11 +23,11 @@ function init (client, $) { eventDate.val(time.format('YYYY-MM-DD')); } - function mergeDateAndTime ( ) { + function mergeDateAndTime () { return client.utils.mergeInputTime(eventTime.val(), eventDate.val()); } - function updateTime(ele, time) { + function updateTime (ele, time) { ele.attr('oldminutes', time.minutes()); ele.attr('oldhours', time.hours()); } @@ -44,14 +39,29 @@ function init (client, $) { } var inputMatrix = {}; + var submitHooks = {}; + + function refreshEventTypes() { + careportal.allEventTypes = client.plugins.getAllEventTypes(client.sbx); + + careportal.events = _.map(careportal.allEventTypes, function each (event) { + return _.pick(event, ['val', 'name']); + }); + + inputMatrix = {}; + 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']); + submitHooks[event.val] = event.submitHook; + }); + } - _.forEach(careportal.allEventTypes, function each (event) { - inputMatrix[event.val] = _.pick(event, ['bg', 'insulin', 'carbs', 'prebolus', 'duration', 'percent', 'absolute', 'profile', 'split', 'reasons', 'targets']); - }); + refreshEventTypes(); - careportal.filterInputs = function filterInputs ( event ) { + careportal.filterInputs = function filterInputs (event) { var eventType = $('#eventType').val(); - + function displayType (enabled) { if (enabled) { return ''; @@ -59,36 +69,40 @@ function init (client, $) { return 'none'; } } - - function resetIfHidden(visible, id) { + + function resetIfHidden (visible, id) { if (!visible) { $(id).val(''); } } var reasons = inputMatrix[eventType]['reasons']; - $('#reasonLabel').css('display',displayType(reasons && reasons.length > 0)); - $('#targets').css('display',displayType(inputMatrix[eventType]['targets'])); - - $('#bg').css('display',displayType(inputMatrix[eventType]['bg'])); - $('#insulinGivenLabel').css('display',displayType(inputMatrix[eventType]['insulin'])); - $('#carbsGivenLabel').css('display',displayType(inputMatrix[eventType]['carbs'])); - $('#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() === '')); - $('#profileLabel').css('display',displayType(inputMatrix[eventType]['profile'])); - $('#preBolusLabel').css('display',displayType(inputMatrix[eventType]['prebolus'])); - $('#insulinSplitLabel').css('display',displayType(inputMatrix[eventType]['split'])); + $('#reasonLabel').css('display', displayType(reasons && reasons.length > 0)); + $('#targets').css('display', displayType(inputMatrix[eventType]['targets'])); + + $('#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() === '')); + $('#profileLabel').css('display', displayType(inputMatrix[eventType]['profile'])); + $('#preBolusLabel').css('display', displayType(inputMatrix[eventType]['prebolus'])); + $('#insulinSplitLabel').css('display', displayType(inputMatrix[eventType]['split'])); $('#reason').empty(); _.each(reasons, function eachReason (reason) { - $('#reason').append(''); + $('#reason').append(''); }); careportal.reasonable(); resetIfHidden(inputMatrix[eventType]['insulin'], '#insulinGiven'); resetIfHidden(inputMatrix[eventType]['carbs'], '#carbsGiven'); + resetIfHidden(inputMatrix[eventType]['protein'], '#proteinGiven'); + resetIfHidden(inputMatrix[eventType]['fat'], '#fatGiven'); resetIfHidden(inputMatrix[eventType]['duration'], '#duration'); resetIfHidden(inputMatrix[eventType]['absolute'], '#absolute'); resetIfHidden(inputMatrix[eventType]['percent'], '#percent'); @@ -99,7 +113,7 @@ function init (client, $) { maybePrevent(event); }; - careportal.reasonable = function reasonable ( ) { + careportal.reasonable = function reasonable () { var eventType = $('#eventType').val(); var reasons = inputMatrix[eventType]['reasons']; var selected = $('#reason').val(); @@ -129,10 +143,10 @@ function init (client, $) { } }; - careportal.prepareEvents = function prepareEvents ( ) { + careportal.prepareEvents = function prepareEvents () { $('#eventType').empty(); - _.each(careportal.events, function eachEvent(event) { - $('#eventType').append(''); + _.each(careportal.events, function eachEvent (event) { + $('#eventType').append(''); }); $('#eventType').change(careportal.filterInputs); $('#reason').change(careportal.reasonable); @@ -144,7 +158,7 @@ function init (client, $) { careportal.adjustSplit(); }; - careportal.adjustSplit = function adjustSplit(event) { + careportal.adjustSplit = function adjustSplit (event) { if ($(this).attr('id') === 'insulinSplitNow') { var nowval = parseInt($('#insulinSplitNow').val()) || 0; $('#insulinSplitExt').val(100 - nowval); @@ -154,12 +168,12 @@ function init (client, $) { $('#insulinSplitNow').val(100 - extval); $('#insulinSplitExt').val(extval); } - + maybePrevent(event); }; - - careportal.resolveEventName = function resolveEventName(value) { - _.each(careportal.events, function eachEvent(e) { + + careportal.resolveEventName = function resolveEventName (value) { + _.each(careportal.events, function eachEvent (e) { if (e.val === value) { value = e.name; } @@ -167,9 +181,11 @@ function init (client, $) { return value; }; - careportal.prepare = function prepare ( ) { + careportal.prepare = function prepare () { + refreshEventTypes(); + $('#profile').empty(); - client.profilefunctions.listBasalProfiles().forEach(function (p) { + client.profilefunctions.listBasalProfiles().forEach(function(p) { $('#profile').append(''); }); careportal.prepareEvents(); @@ -177,6 +193,8 @@ function init (client, $) { $('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + client.settings.units); $('#meter').prop('checked', true); $('#carbsGiven').val(''); + $('#proteinGiven').val(''); + $('#fatGiven').val(''); $('#insulinGiven').val(''); $('#duration').val(''); $('#percent').val(''); @@ -189,29 +207,43 @@ function init (client, $) { setDateAndTime(); }; - function gatherData ( ) { + function gatherData () { + var eventType = $('#eventType').val(); + var selectedReason = $('#reason').val(); + var data = { enteredBy: $('#enteredBy').val() - , eventType: $('#eventType').val() - , glucose: $('#glucoseValue').val().replace(',','.') - , reason: $('#reason').val() - , targetTop: $('#targetTop').val().replace(',','.') - , targetBottom: $('#targetBottom').val().replace(',','.') - , glucoseType: $('#treatment-form').find('input[name=glucoseType]:checked').val() - , carbs: $('#carbsGiven').val() - , insulin: $('#insulinGiven').val() - , 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()) - , notes: $('#notes').val() - , units: client.settings.units + , eventType: eventType + , glucose: $('#glucoseValue').val().replace(',', '.') + , reason: selectedReason + , targetTop: $('#targetTop').val().replace(',', '.') + , targetBottom: $('#targetBottom').val().replace(',', '.') + , glucoseType: $('#treatment-form').find('input[name=glucoseType]:checked').val() + , carbs: $('#carbsGiven').val() + , protein: $('#proteinGiven').val() + , fat: $('#fatGiven').val() + , insulin: $('#insulinGiven').val() + , 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()) + , notes: $('#notes').val() + , units: client.settings.units }; - if (units == "mmol") { - data.targetTop = data.targetTop * 18; - data.targetBottom = data.targetBottom * 18; - } + var reasons = inputMatrix[eventType]['reasons']; + var reason = _.find(reasons, function matches (r) { + return r.name === selectedReason; + }); + + if (reason) { + data.reasonDisplay = reason.displayName; + } + + if (units == "mmol") { + data.targetTop = data.targetTop * consts.MMOL_TO_MGDL; + data.targetBottom = data.targetBottom * consts.MMOL_TO_MGDL; + } //special handling for absolute to support temp to 0 var absolute = $('#absolute').val(); @@ -222,7 +254,7 @@ function init (client, $) { if ($('#othertime').is(':checked')) { data.eventTime = mergeDateAndTime().toDate(); } - + if (!inputMatrix[data.eventType].profile) { delete data.profile; } @@ -250,7 +282,59 @@ function init (client, $) { maybePrevent(event); }; - function buildConfirmText(data) { + function validateData (data) { + + let allOk = true; + let messages = []; + + console.log('Validating careportal entry: ', data.eventType); + + if (data.duration !== 0 && data.eventType == 'Temporary Target') { + if (isNaN(data.targetTop) || isNaN(data.targetBottom) || !data.targetBottom || !data.targetTop) { + console.log('Bottom or Top target missing'); + allOk = false; + messages.push("Please enter a valid value for both top and bottom target to save a Temporary Target"); + } else { + + let targetTop = parseInt(data.targetTop); + let targetBottom = parseInt(data.targetBottom); + + let minTarget = 4 * consts.MMOL_TO_MGDL; + let maxTarget = 18 * consts.MMOL_TO_MGDL; + + if (units == "mmol") { + targetTop = Math.round(targetTop / consts.MMOL_TO_MGDL * 10) / 10; + targetBottom = Math.round(targetBottom / consts.MMOL_TO_MGDL * 10) / 10; + minTarget = Math.round(minTarget / consts.MMOL_TO_MGDL * 10) / 10; + maxTarget = Math.round(maxTarget / consts.MMOL_TO_MGDL * 10) / 10; + } + + if (targetTop > maxTarget) { + allOk = false; + messages.push("Temporary target high is too high"); + } + + if (targetBottom < minTarget) { + allOk = false; + messages.push("Temporary target low is too low"); + } + + if (targetTop < targetBottom || targetBottom > targetTop) { + allOk = false; + messages.push("The low target must be lower than the high target and high target must be higher than the low target."); + } + + } + } + + return { + allOk + , messages + }; + + } + + function buildConfirmText (data) { var text = [ translate('Please verify that the data entered is correct') + ': ' , translate('Event Type') + ': ' + translate(careportal.resolveEventName(data.eventType)) @@ -263,26 +347,28 @@ function init (client, $) { } if (data.duration === 0 && data.eventType === 'Temporary Target') { - text[text.length - 1] += ' ' + translate('Cancel'); + text[text.length - 1] += ' ' + translate('Cancel'); } pushIf(data.glucose, translate('Blood Glucose') + ': ' + data.glucose); pushIf(data.glucose, translate('Measurement Method') + ': ' + translate(data.glucoseType)); pushIf(data.reason, translate('Reason') + ': ' + data.reason); - + var targetTop = data.targetTop; var targetBottom = data.targetBottom; - + if (units == "mmol") { - targetTop = Math.round(data.targetTop / 18.0 * 10) / 10; - targetBottom = Math.round(data.targetBottom / 18.0 * 10) / 10; - } - + targetTop = Math.round(data.targetTop / consts.MMOL_TO_MGDL * 10) / 10; + targetBottom = Math.round(data.targetBottom / consts.MMOL_TO_MGDL * 10) / 10; + } + pushIf(data.targetTop, translate('Target Top') + ': ' + targetTop); pushIf(data.targetBottom, translate('Target Bottom') + ': ' + targetBottom); pushIf(data.carbs, translate('Carbs Given') + ': ' + data.carbs); + pushIf(data.protein, translate('Protein Given') + ': ' + data.protein); + pushIf(data.fat, translate('Fat Given') + ': ' + data.fat); pushIf(data.insulin, translate('Insulin Given') + ': ' + data.insulin); pushIf(data.eventType === 'Combo Bolus', translate('Combo Bolus') + ': ' + data.splitNow + '% : ' + data.splitExt + '%'); pushIf(data.duration, translate('Duration') + ': ' + data.duration + ' ' + translate('mins')); @@ -297,13 +383,39 @@ function init (client, $) { return text.join('\n'); } - function confirmPost(data) { - if (window.confirm(buildConfirmText(data))) { - postTreatment(data); + function confirmPost (data) { + + const validation = validateData(data); + + if (!validation.allOk) { + + let messages = ""; + + validation.messages.forEach(function(m) { + messages += translate(m) + "\n"; + }); + + window.alert(messages); + } else { + if (window.confirm(buildConfirmText(data))) { + var submitHook = submitHooks[data.eventType]; + if (submitHook) { + submitHook(client, data, function (error) { + if (error) { + console.log("submit error = ", error); + alert(translate('Error') + ': ' + error); + } else { + client.browserUtils.closeDrawer('#treatmentDrawer'); + } + }); + } else { + postTreatment(data); + } + } } } - function postTreatment(data) { + function postTreatment (data) { if (data.eventType === 'Combo Bolus') { data.enteredinsulin = data.insulin; data.insulin = data.enteredinsulin * data.splitNow / 100; @@ -312,9 +424,9 @@ function init (client, $) { $.ajax({ method: 'POST' - , url: '/api/v1/treatments/' - , headers: client.headers() - , data: data + , url: '/api/v1/treatments/' + , headers: client.headers() + , data: data }).done(function treatmentSaved (response) { console.info('treatment saved', response); }).fail(function treatmentSaveFail (response) { @@ -326,7 +438,7 @@ function init (client, $) { client.browserUtils.closeDrawer('#treatmentDrawer'); } - + careportal.dateTimeFocus = function dateTimeFocus (event) { $('#othertime').prop('checked', true); updateTime($(this), mergeDateAndTime()); @@ -374,4 +486,3 @@ function init (client, $) { } module.exports = init; - diff --git a/lib/client/chart.js b/lib/client/chart.js index 32a15ba0155..a5db09f416a 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -1,12 +1,24 @@ 'use strict'; -// var _ = require('lodash'); +var _ = require('lodash'); var times = require('../times'); var d3locales = require('./d3locales'); -var padding = { bottom: 30 }; +var scrolling = false + , scrollNow = 0 + , scrollBrushExtent = null + , scrollRange = null; + +var PADDING_BOTTOM = 30 + , OPEN_TOP_HEIGHT = 8 + , CONTEXT_MAX = 420 + , CONTEXT_MIN = 36 + , FOCUS_MAX = 510 + , FOCUS_MIN = 30; + +var loadTime = Date.now(); function init (client, d3, $) { - var chart = { }; + var chart = {}; var utils = client.utils; var renderer = client.renderer; @@ -23,146 +35,186 @@ function init (client, d3, $) { .attr('x', 0) .attr('y', 0) .append('g') - .style('fill', 'none') - .style('stroke', '#0099ff') - .style('stroke-width', 2) + .style('fill', 'none') + .style('stroke', '#0099ff') + .style('stroke-width', 2) .append('path').attr('d', 'M0,0 l' + dashWidth + ',' + dashWidth) .append('path').attr('d', 'M' + dashWidth + ',0 l-' + dashWidth + ',' + dashWidth); - + // arrow head defs.append('marker') - .attr({ - 'id': 'arrow', - 'viewBox': '0 -5 10 10', - 'refX': 5, - 'refY': 0, - 'markerWidth': 8, - 'markerHeight': 8, - 'orient': 'auto' - }) + .attr('id', 'arrow') + .attr('viewBox', '0 -5 10 10') + .attr('refX', 5) + .attr('refY', 0) + .attr('markerWidth', 8) + .attr('markerHeight', 8) + .attr('orient', 'auto') .append('path') - .attr('d', 'M0,-5L10,0L0,5') - .attr('class', 'arrowHead'); + .attr('d', 'M0,-5L10,0L0,5') + .attr('class', 'arrowHead'); + + var localeFormatter = d3.timeFormatLocale(d3locales.locale(client.settings.language)); + + function beforeBrushStarted () { + // go ahead and move the brush because + // a single click will not execute the brush event + var now = new Date(); + var dx = chart.xScale2(now) - chart.xScale2(new Date(now.getTime() - client.focusRangeMS)); - var localeFormatter = d3.locale(d3locales.locale(client.settings.language)); - - function brushStarted ( ) { + var cx = d3.mouse(this)[0]; + var x0 = cx - dx / 2; + var x1 = cx + dx / 2; + + var range = chart.xScale2.range(); + var X0 = range[0]; + var X1 = range[1]; + + var brush = x0 < X0 ? [X0, X0 + dx] : x1 > X1 ? [X1 - dx, X1] : [x0, x1]; + + chart.theBrush.call(chart.brush.move, brush); + } + + function brushStarted () { // update the opacity of the context data points to brush extent chart.context.selectAll('circle') .data(client.entries) .style('opacity', 1); } - function brushEnded ( ) { + function brushEnded () { // update the opacity of the context data points to brush extent chart.context.selectAll('circle') .data(client.entries) - .style('opacity', function (d) { return renderer.highlightBrushPoints(d) }); + .style('opacity', function(d) { return renderer.highlightBrushPoints(d) }); } var extent = client.dataExtent(); var yScaleType; if (client.settings.scaleY === 'linear') { - yScaleType = d3.scale.linear; + yScaleType = d3.scaleLinear; } else { - yScaleType = d3.scale.log; + yScaleType = d3.scaleLog; } - var focusYDomain = [utils.scaleMgdl(30), utils.scaleMgdl(510)]; - var contextYDomain = [utils.scaleMgdl(36), utils.scaleMgdl(420)]; + var focusYDomain = [utils.scaleMgdl(FOCUS_MIN), utils.scaleMgdl(FOCUS_MAX)]; + var contextYDomain = [utils.scaleMgdl(CONTEXT_MIN), utils.scaleMgdl(CONTEXT_MAX)]; - function dynamicDomain() { + function dynamicDomain () { // allow y-axis to extend all the way to the top of the basal area, but leave room to display highest value var mult = 1.15 , targetTop = client.settings.thresholds.bgTargetTop // filter to only use actual SGV's (not rawbg's) to set the view window. // can switch to Logarithmic (non-dynamic) to see anything that doesn't fit in the dynamicDomain - , mgdlMax = d3.max(client.entries, function (d) { if ( d.type === 'sgv') { return d.mgdl; } }); - // use the 99th percentile instead of max to avoid rescaling for 1 flukey data point - // need to sort client.entries by mgdl first - //, mgdlMax = d3.quantile(client.entries, 0.99, function (d) { return d.mgdl; }); + , mgdlMax = d3.max(client.entries, function(d) { if (d.type === 'sgv') { return d.mgdl; } }); + // use the 99th percentile instead of max to avoid rescaling for 1 flukey data point + // need to sort client.entries by mgdl first + //, mgdlMax = d3.quantile(client.entries, 0.99, function (d) { return d.mgdl; }); return [ - utils.scaleMgdl(30) + utils.scaleMgdl(FOCUS_MIN) , Math.max(utils.scaleMgdl(mgdlMax * mult), utils.scaleMgdl(targetTop * mult)) ]; } - function dynamicDomainOrElse(defaultDomain) { - if (client.settings.scaleY === 'linear' || client.settings.scaleY === 'log-dynamic') { + function dynamicDomainOrElse (defaultDomain) { + if (client.entries && (client.entries.length > 0) && (client.settings.scaleY === 'linear' || client.settings.scaleY === 'log-dynamic')) { return dynamicDomain(); } else { return defaultDomain; } } - + // define the parts of the axis that aren't dependent on width or height - var xScale = chart.xScale = d3.time.scale().domain(extent); + var xScale = chart.xScale = d3.scaleTime().domain(extent); + focusYDomain = dynamicDomainOrElse(focusYDomain); var yScale = chart.yScale = yScaleType() - .domain(dynamicDomainOrElse(focusYDomain)); + .domain(focusYDomain); - var xScale2 = chart.xScale2 = d3.time.scale().domain(extent); + var xScale2 = chart.xScale2 = d3.scaleTime().domain(extent); + + contextYDomain = dynamicDomainOrElse(contextYDomain); var yScale2 = chart.yScale2 = yScaleType() - .domain(dynamicDomainOrElse(contextYDomain)); + .domain(contextYDomain); - chart.xScaleBasals = d3.time.scale().domain(extent); + chart.xScaleBasals = d3.scaleTime().domain(extent); - chart.yScaleBasals = d3.scale.linear() + chart.yScaleBasals = d3.scaleLinear() .domain([0, 5]); - var tickFormat = localeFormatter.timeFormat.multi( [ - ['.%L', function(d) { return d.getMilliseconds(); }], - [':%S', function(d) { return d.getSeconds(); }], - [client.settings.timeFormat === 24 ? '%H:%M' : '%I:%M', function(d) { return d.getMinutes(); }], - [client.settings.timeFormat === 24 ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], - ['%a %d', function(d) { return d.getDay() && d.getDate() !== 1; }], - ['%b %d', function(d) { return d.getDate() !== 1; }], - ['%B', function(d) { return d.getMonth(); }], - ['%Y', function() { return true; }] - ]); + var formatMillisecond = localeFormatter.format('.%L') + , formatSecond = localeFormatter.format(':%S') + , formatMinute = client.settings.timeFormat === 24 ? localeFormatter.format('%H:%M') : + localeFormatter.format('%-I:%M') + , formatHour = client.settings.timeFormat === 24 ? localeFormatter.format('%H:%M') : + localeFormatter.format('%-I %p') + , formatDay = localeFormatter.format('%a %d') + , formatWeek = localeFormatter.format('%b %d') + , formatMonth = localeFormatter.format('%B') + , formatYear = localeFormatter.format('%Y'); + + var tickFormat = function(date) { + return (d3.timeSecond(date) < date ? formatMillisecond : + d3.timeMinute(date) < date ? formatSecond : + d3.timeHour(date) < date ? formatMinute : + d3.timeDay(date) < date ? formatHour : + d3.timeMonth(date) < date ? (d3.timeWeek(date) < date ? formatDay : formatWeek) : + d3.timeYear(date) < date ? formatMonth : + formatYear)(date); + }; var tickValues = client.ticks(client); - chart.xAxis = d3.svg.axis() - .scale(xScale) + chart.xAxis = d3.axisBottom(xScale) + + chart.xAxis = d3.axisBottom(xScale) .tickFormat(tickFormat) - .ticks(4) - .orient('bottom'); + .ticks(6); - chart.yAxis = d3.svg.axis() - .scale(yScale) + chart.yAxis = d3.axisLeft(yScale) .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); + .tickValues(tickValues); - chart.xAxis2 = d3.svg.axis() - .scale(xScale2) + chart.xAxis2 = d3.axisBottom(xScale2) .tickFormat(tickFormat) - .ticks(6) - .orient('bottom'); + .ticks(6); - chart.yAxis2 = d3.svg.axis() - .scale(yScale2) + chart.yAxis2 = d3.axisRight(yScale2) .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('right'); + .tickValues(tickValues); + + d3.select('tick') + .style('z-index', '10000'); // setup a brush - chart.brush = d3.svg.brush() - .x(xScale2) - .on('brushstart', brushStarted) + chart.brush = d3.brushX() + .on('start', brushStarted) .on('brush', function brush (time) { - client.loadRetroIfNeeded(); + // layouting the graph causes a brushed event + // ignore retro data load the first two seconds + if (Date.now() - loadTime > 2000) client.loadRetroIfNeeded(); client.brushed(time); }) - .on('brushend', brushEnded); + .on('end', brushEnded); + + chart.theBrush = null; - chart.futureOpacity = d3.scale.linear( ) - .domain([times.mins(25).msecs, times.mins(60).msecs]) - .range([0.8, 0.1]); + chart.futureOpacity = (function() { + var scale = d3.scaleLinear() + .domain([times.mins(25).msecs, times.mins(60).msecs]) + .range([0.8, 0.1]); + + return function(delta) { + if (delta < 0) { + return null; + } else { + return scale(delta); + } + }; + })(); // create svg and g to contain the chart contents chart.charts = d3.select('#chartContainer').append('svg') @@ -176,54 +228,79 @@ function init (client, d3, $) { // create the x axis container chart.focus.append('g') - .attr('class', 'x axis'); + .attr('class', 'x axis') + .style("font-size", "16px"); // create the y axis container chart.focus.append('g') - .attr('class', 'y axis'); + .attr('class', 'y axis') + .style("font-size", "16px"); - chart.context = chart.charts.append('g').attr('class', 'chart-context'); + chart.context = chart.charts.append('g') + .attr('class', 'chart-context'); // create the x axis container chart.context.append('g') - .attr('class', 'x axis'); + .attr('class', 'x axis') + .style("font-size", "16px"); // create the y axis container chart.context.append('g') - .attr('class', 'y axis'); - - function createAdjustedRange() { - var range = chart.brush.extent().slice(); + .attr('class', 'y axis') + .style("font-size", "16px"); + + 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(); + + if (!brushedRange) { + // console.log('No current brushed range. Setting range to last focusRangeMS amount of available data'); + range = dataExtent; + range[0] = new Date(range[1].getTime() - client.focusRangeMS); + } - var end = range[1].getTime() + client.forecastTime; + var end = range[1].getTime(); if (!chart.inRetroMode()) { - var lastSGVMills = client.latestSGV ? client.latestSGV.mills : client.now; - end += (client.now - lastSGVMills); + end = client.now > dataExtent[1].getTime() ? client.now : dataExtent[1].getTime(); } range[1] = new Date(end); + range[0] = new Date(end - client.focusRangeMS); return range; } - chart.inRetroMode = function inRetroMode() { - if (!chart.brush || !chart.xScale2) { + chart.createAdjustedRange = function() { + var adjustedRange = chart.createBrushedRange(); + + adjustedRange[1] = new Date(adjustedRange[1].getTime() + client.forecastTime); + + return adjustedRange; + } + + chart.inRetroMode = function inRetroMode () { + var brushedRange = chart.theBrush && d3.brushSelection(chart.theBrush.node()) || null; + + if (!brushedRange || !chart.xScale2) { return false; } - var brushTime = chart.brush.extent()[1].getTime(); var maxTime = chart.xScale2.domain()[1].getTime(); + var brushTime = chart.xScale2.invert(brushedRange[1]).getTime(); return brushTime < maxTime; }; // called for initial update and updates for resize - chart.update = function update(init) { + chart.update = function update (init) { if (client.documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); return; } + chart.setForecastTime(); + var chartContainer = $('#chartContainer'); if (chartContainer.length < 1) { @@ -235,15 +312,16 @@ function init (client, d3, $) { var dataRange = client.dataExtent(); var chartContainerRect = chartContainer[0].getBoundingClientRect(); var chartWidth = chartContainerRect.width; - var chartHeight = chartContainerRect.height - padding.bottom; + var chartHeight = chartContainerRect.height - PADDING_BOTTOM; // get the height of each chart based on its container size ratio var focusHeight = chart.focusHeight = chartHeight * .7; - var contextHeight = chart.contextHeight = chartHeight * .2; + var contextHeight = chart.contextHeight = chartHeight * .3; chart.basalsHeight = focusHeight / 4; // get current brush extent - var currentBrushExtent = createAdjustedRange(); + var currentRange = chart.createAdjustedRange(); + var currentBrushExtent = chart.createBrushedRange(); // only redraw chart if chart size has changed var widthChanged = (chart.prevChartWidth !== chartWidth); @@ -259,14 +337,14 @@ function init (client, d3, $) { //set the width and height of the SVG element chart.charts.attr('width', chartWidth) - .attr('height', chartHeight + padding.bottom); + .attr('height', chartHeight + PADDING_BOTTOM); // ranges are based on the width and height available so reset chart.xScale.range([0, chartWidth]); chart.xScale2.range([0, chartWidth]); chart.xScaleBasals.range([0, chartWidth]); chart.yScale.range([focusHeight, 0]); - chart.yScale2.range([chartHeight, chartHeight - contextHeight]); + chart.yScale2.range([contextHeight, 0]); chart.yScaleBasals.range([0, focusHeight / 4]); if (init) { @@ -281,42 +359,48 @@ function init (client, d3, $) { .call(chart.yAxis); // if first run then just display axis with no transition + chart.context + .attr('transform', 'translate(0,' + focusHeight + ')') + chart.context.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') + .attr('transform', 'translate(0,' + contextHeight + ')') .call(chart.xAxis2); -// chart.basals.select('.y') -// .attr('transform', 'translate(0,' + 0 + ')') -// .call(chart.yAxisBasals); - - chart.context.append('g') + chart.theBrush = chart.context.append('g') .attr('class', 'x brush') - .call(d3.svg.brush().x(chart.xScale2).on('brush', client.brushed)) - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); + .call(chart.brush) + .call(g => g.select(".overlay") + .datum({ type: 'selection' }) + .on('mousedown touchstart', beforeBrushStarted)); + + chart.theBrush.selectAll('rect') + .attr('y', 0) + .attr('height', contextHeight); // disable resizing of brush - d3.select('.x.brush').select('.background').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); + chart.context.select('.x.brush').select('.overlay').style('cursor', 'move'); + chart.context.select('.x.brush').selectAll('.handle') + .style('cursor', 'move'); + + chart.context.select('.x.brush').select('.selection') + .style('visibility', 'hidden'); // add a line that marks the current time chart.focus.append('line') .attr('class', 'now-line') .attr('x1', chart.xScale(new Date(client.now))) - .attr('y1', chart.yScale(utils.scaleMgdl(30))) + .attr('y1', chart.yScale(focusYDomain[0])) .attr('x2', chart.xScale(new Date(client.now))) - .attr('y2', chart.yScale(utils.scaleMgdl(420))) + .attr('y2', chart.yScale(focusYDomain[1])) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); // add a y-axis line that shows the high bg threshold chart.focus.append('line') .attr('class', 'high-line') - .attr('x1', chart.xScale(dataRange[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgHigh))) - .attr('x2', chart.xScale(dataRange[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgHigh))) .style('stroke-dasharray', ('1, 6')) .attr('stroke', '#777'); @@ -324,9 +408,9 @@ function init (client, d3, $) { // add a y-axis line that shows the high bg threshold chart.focus.append('line') .attr('class', 'target-top-line') - .attr('x1', chart.xScale(dataRange[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))) - .attr('x2', chart.xScale(dataRange[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -334,9 +418,9 @@ function init (client, d3, $) { // add a y-axis line that shows the low bg threshold chart.focus.append('line') .attr('class', 'target-bottom-line') - .attr('x1', chart.xScale(dataRange[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))) - .attr('x2', chart.xScale(dataRange[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -344,9 +428,9 @@ function init (client, d3, $) { // add a y-axis line that shows the low bg threshold chart.focus.append('line') .attr('class', 'low-line') - .attr('x1', chart.xScale(dataRange[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow))) - .attr('x2', chart.xScale(dataRange[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow))) .style('stroke-dasharray', ('1, 6')) .attr('stroke', '#777'); @@ -355,7 +439,7 @@ function init (client, d3, $) { chart.context.append('line') .attr('class', 'open-top') .attr('stroke', '#111') - .attr('stroke-width', 12); + .attr('stroke-width', OPEN_TOP_HEIGHT); // add a x-axis line that closes the the brush container on left side chart.context.append('line') @@ -371,9 +455,9 @@ function init (client, d3, $) { chart.context.append('line') .attr('class', 'now-line') .attr('x1', chart.xScale(new Date(client.now))) - .attr('y1', chart.yScale2(utils.scaleMgdl(36))) + .attr('y1', chart.yScale2(contextYDomain[0])) .attr('x2', chart.xScale(new Date(client.now))) - .attr('y2', chart.yScale2(utils.scaleMgdl(420))) + .attr('y2', chart.yScale2(contextYDomain[1])) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -400,96 +484,82 @@ function init (client, d3, $) { } else { // for subsequent updates use a transition to animate the axis to the new position - var focusTransition = chart.focus.transition(); - focusTransition.select('.x') + chart.focus.select('.x') .attr('transform', 'translate(0,' + focusHeight + ')') .call(chart.xAxis); - focusTransition.select('.y') + chart.focus.select('.y') .attr('transform', 'translate(' + chartWidth + ', 0)') .call(chart.yAxis); - var contextTransition = chart.context.transition(); + chart.context + .attr('transform', 'translate(0,' + focusHeight + ')') - contextTransition.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') + chart.context.select('.x') + .attr('transform', 'translate(0,' + contextHeight + ')') .call(chart.xAxis2); - chart.basals.transition(); - -// basalsTransition.select('.y') -// .attr('transform', 'translate(0,' + 0 + ')') -// .call(chart.yAxisBasals); + chart.basals; // reset brush location - chart.context.select('.x.brush') - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); + chart.theBrush.selectAll('rect') + .attr('y', 0) + .attr('height', contextHeight); - // clear current brushs - d3.select('.brush').call(chart.brush.clear()); + // console.log('Redrawing old brush with new dimensions: ', currentBrushExtent); // redraw old brush with new dimensions - d3.select('.brush').transition().call(chart.brush.extent(currentBrushExtent)); + chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); // transition lines to correct location chart.focus.select('.high-line') - .transition() - .attr('x1', chart.xScale(currentBrushExtent[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgHigh))) - .attr('x2', chart.xScale(currentBrushExtent[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgHigh))); chart.focus.select('.target-top-line') - .transition() - .attr('x1', chart.xScale(currentBrushExtent[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))) - .attr('x2', chart.xScale(currentBrushExtent[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))); chart.focus.select('.target-bottom-line') - .transition() - .attr('x1', chart.xScale(currentBrushExtent[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))) - .attr('x2', chart.xScale(currentBrushExtent[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))); chart.focus.select('.low-line') - .transition() - .attr('x1', chart.xScale(currentBrushExtent[0])) + .attr('x1', chart.xScale.range()[0]) .attr('y1', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow))) - .attr('x2', chart.xScale(currentBrushExtent[1])) + .attr('x2', chart.xScale.range()[1]) .attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow))); // transition open-top line to correct location chart.context.select('.open-top') - .transition() - .attr('x1', chart.xScale2(currentBrushExtent[0])) - .attr('y1', chart.yScale(utils.scaleMgdl(30))) - .attr('x2', chart.xScale2(currentBrushExtent[1])) - .attr('y2', chart.yScale(utils.scaleMgdl(30))); + .attr('x1', chart.xScale2(currentRange[0])) + .attr('y1', chart.yScale2(utils.scaleMgdl(CONTEXT_MAX)) + Math.floor(OPEN_TOP_HEIGHT/2.0)-1) + .attr('x2', chart.xScale2(currentRange[1])) + .attr('y2', chart.yScale2(utils.scaleMgdl(CONTEXT_MAX)) + Math.floor(OPEN_TOP_HEIGHT/2.0)-1); // transition open-left line to correct location chart.context.select('.open-left') - .transition() - .attr('x1', chart.xScale2(currentBrushExtent[0])) - .attr('y1', focusHeight) - .attr('x2', chart.xScale2(currentBrushExtent[0])) - .attr('y2', chartHeight); + .attr('x1', chart.xScale2(currentRange[0])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentRange[0])) + .attr('y2', chart.yScale2(contextYDomain[1])); // transition open-right line to correct location chart.context.select('.open-right') - .transition() - .attr('x1', chart.xScale2(currentBrushExtent[1])) - .attr('y1', focusHeight) - .attr('x2', chart.xScale2(currentBrushExtent[1])) - .attr('y2', chartHeight); + .attr('x1', chart.xScale2(currentRange[1])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentRange[1])) + .attr('y2', chart.yScale2(contextYDomain[1])); // transition high line to correct location chart.context.select('.high-line') - .transition() .attr('x1', chart.xScale2(dataRange[0])) .attr('y1', chart.yScale2(utils.scaleMgdl(client.settings.thresholds.bgTargetTop))) .attr('x2', chart.xScale2(dataRange[1])) @@ -497,7 +567,6 @@ function init (client, d3, $) { // transition low line to correct location chart.context.select('.low-line') - .transition() .attr('x1', chart.xScale2(dataRange[0])) .attr('y1', chart.yScale2(utils.scaleMgdl(client.settings.thresholds.bgTargetBottom))) .attr('x2', chart.xScale2(dataRange[1])) @@ -505,64 +574,83 @@ function init (client, d3, $) { } } - // update domain - chart.xScale2.domain(dataRange); + chart.updateContext(dataRange); + chart.xScaleBasals.domain(dataRange); - var updateBrush = d3.select('.brush').transition(); - updateBrush - .call(chart.brush.extent([new Date(dataRange[1].getTime() - client.focusRangeMS), dataRange[1]])); - client.brushed(true); + // console.log('Redrawing brush due to update: ', currentBrushExtent); + + chart.theBrush.call(chart.brush.move, currentBrushExtent.map(chart.xScale2)); + }; + + chart.updateContext = function(dataRange_) { + if (client.documentHidden) { + console.info('Document Hidden, not updating - ' + (new Date())); + return; + } + + // get current data range + var dataRange = dataRange_ || client.dataExtent(); + + // update domain + chart.xScale2.domain(dataRange); renderer.addContextCircles(); // update x axis domain chart.context.select('.x').call(chart.xAxis2); - }; - chart.scroll = function scroll (nowDate) { - chart.xScale.domain(createAdjustedRange()); - chart.yScale.domain(dynamicDomainOrElse(focusYDomain)); - chart.xScaleBasals.domain(createAdjustedRange()); + function scrollUpdate () { + scrolling = false; + + var nowDate = scrollNow; + + var currentBrushExtent = scrollBrushExtent; + var currentRange = scrollRange; + + chart.xScale.domain(currentRange); + + focusYDomain = dynamicDomainOrElse(focusYDomain); + + chart.yScale.domain(focusYDomain); + chart.xScaleBasals.domain(currentRange); // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location d3.selectAll('.path').remove(); // transition open-top line to correct location chart.context.select('.open-top') - .attr('x1', chart.xScale2(chart.brush.extent()[0])) - .attr('y1', chart.yScale(utils.scaleMgdl(30))) - .attr('x2', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) - .attr('y2', chart.yScale(utils.scaleMgdl(30))); + .attr('x1', chart.xScale2(currentRange[0])) + .attr('y1', chart.yScale2(contextYDomain[1]) + Math.floor(OPEN_TOP_HEIGHT / 2.0)-1) + .attr('x2', chart.xScale2(currentRange[1])) + .attr('y2', chart.yScale2(contextYDomain[1]) + Math.floor(OPEN_TOP_HEIGHT / 2.0)-1); // transition open-left line to correct location chart.context.select('.open-left') - .attr('x1', chart.xScale2(chart.brush.extent()[0])) - .attr('y1', chart.focusHeight) - .attr('x2', chart.xScale2(chart.brush.extent()[0])) - .attr('y2', chart.prevChartHeight); + .attr('x1', chart.xScale2(currentRange[0])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentRange[0])) + .attr('y2', chart.yScale2(contextYDomain[1])); // transition open-right line to correct location chart.context.select('.open-right') - .attr('x1', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) - .attr('y1', chart.focusHeight) - .attr('x2', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) - .attr('y2', chart.prevChartHeight); + .attr('x1', chart.xScale2(currentRange[1])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentRange[1])) + .attr('y2', chart.yScale2(contextYDomain[1])); chart.focus.select('.now-line') - .transition() .attr('x1', chart.xScale(nowDate)) - .attr('y1', chart.yScale(utils.scaleMgdl(36))) + .attr('y1', chart.yScale(focusYDomain[0])) .attr('x2', chart.xScale(nowDate)) - .attr('y2', chart.yScale(utils.scaleMgdl(420))); + .attr('y2', chart.yScale(focusYDomain[1])); chart.context.select('.now-line') - .transition() - .attr('x1', chart.xScale2(chart.brush.extent()[1])) - .attr('y1', chart.yScale2(utils.scaleMgdl(36))) - .attr('x2', chart.xScale2(chart.brush.extent()[1])) - .attr('y2', chart.yScale2(utils.scaleMgdl(420))); + .attr('x1', chart.xScale2(currentBrushExtent[1])) + .attr('y1', chart.yScale2(contextYDomain[0])) + .attr('x2', chart.xScale2(currentBrushExtent[1])) + .attr('y2', chart.yScale2(contextYDomain[1])); // update x,y axis chart.focus.select('.x.axis').call(chart.xAxis); @@ -571,10 +659,67 @@ function init (client, d3, $) { renderer.addBasals(client); renderer.addFocusCircles(); - renderer.addTreatmentCircles(); + renderer.addTreatmentCircles(nowDate); renderer.addTreatmentProfiles(client); renderer.drawTreatments(client); + } + + chart.scroll = function scroll (nowDate) { + scrollNow = nowDate; + scrollBrushExtent = chart.createBrushedRange(); + scrollRange = chart.createAdjustedRange(); + + if (!scrolling) { + requestAnimationFrame(scrollUpdate); + } + + scrolling = true; + }; + + chart.getMaxForecastMills = function getMaxForecastMills () { + // limit lookahead to the same as lookback + var selectedRange = chart.createBrushedRange(); + var to = selectedRange[1].getTime(); + return to + client.focusRangeMS; + }; + + chart.getForecastData = function getForecastData () { + + var maxForecastAge = chart.getMaxForecastMills(); + var pointTypes = client.settings.showForecast.split(' '); + + var points = pointTypes.reduce( function (points, type) { + return points.concat(client.sbx.pluginBase.forecastPoints[type] || []); + }, [] ); + + return _.filter(points, function isShown (point) { + return point.mills < maxForecastAge; + }); + + }; + + chart.setForecastTime = function setForecastTime () { + + if (client.sbx.pluginBase.forecastPoints) { + var shownForecastPoints = chart.getForecastData(); + + var focusHoursAheadMills = chart.getMaxForecastMills(); + + var selectedRange = chart.createBrushedRange(); + var to = selectedRange[1].getTime(); + var maxForecastMills = to + times.mins(30).msecs; + + if (shownForecastPoints.length > 0) { + maxForecastMills = _.max(_.map(shownForecastPoints, function(point) { return point.mills })); + } + + maxForecastMills = Math.min(focusHoursAheadMills, maxForecastMills); + + var lastSGVMills = client.sbx.lastSGVMills(); + + client.forecastTime = ((maxForecastMills > 0) && lastSGVMills) ? maxForecastMills - lastSGVMills : client.defaultForecastTime; + } }; return chart; diff --git a/lib/client/clock-client.js b/lib/client/clock-client.js new file mode 100644 index 00000000000..324a5168693 --- /dev/null +++ b/lib/client/clock-client.js @@ -0,0 +1,200 @@ +'use strict'; + +const browserSettings = require('./browser-settings'); + +var client = {}; + +client.settings = browserSettings(client, window.serverSettings, $); + +// console.log('settings', client.settings); +// client.settings now contains all settings + +client.query = function query () { + console.log('query'); + var parts = (location.search || '?').substring(1).split('&'); + var token = ''; + parts.forEach(function (val) { + if (val.startsWith('token=')) { + token = val.substring('token='.length); + } + }); + + var secret = localStorage.getItem('apisecrethash'); + var src = '/api/v1/entries.json?count=3&t=' + new Date().getTime(); + + if (secret) { + src += '&secret=' + secret; + } else if (token) { + src += '&token=' + token; + } + + $.ajax(src, { + success: client.render + }); +}; + +client.render = function render (xhr) { + console.log('got data', xhr); + + let rec; + let delta; + + xhr.forEach(element => { + if (element.sgv && !rec) { + rec = element; + } + else if (element.sgv && rec && delta==null) { + delta = (rec.sgv - element.sgv)/((rec.date - element.date)/(5*60*1000)); + } + }); + + let $errorMessage = $('#errorMessage'); + + // If no one measured value found => show "-?-" + if (!rec) { + if (!$errorMessage.length) { + $('#arrowDiv').append('
-?-
'); + $('#arrow').hide(); + } else { + $errorMessage.show(); + } + return; + } else { + $errorMessage.length && $errorMessage.hide(); + $('#arrow').show(); + } + + let last = new Date(rec.date); + let now = new Date(); + + // Convert BG to mmol/L if necessary. + if (window.serverSettings.settings.units === 'mmol') { + var displayValue = window.Nightscout.units.mgdlToMMOL(rec.sgv); + var deltaDisplayValue = window.Nightscout.units.mgdlToMMOL(delta); + } else { + displayValue = rec.sgv; + deltaDisplayValue = Math.round(delta); + } + + if (deltaDisplayValue > 0) { + 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)); + + // Generate and insert the clock. + let timeDivisor = parseInt(client.settings.timeFormat ? client.settings.timeFormat : 12, 10); + let today = new Date() + , h = today.getHours() % timeDivisor; + if (timeDivisor === 12) { + h = (h === 0) ? 12 : h; // In the case of 00:xx, change to 12:xx for 12h time + } + if (timeDivisor === 24) { + h = (h < 10) ? ("0" + h) : h; // Pad the hours with a 0 in 24h time + } + let m = today.getMinutes(); + if (m < 10) m = "0" + m; + $('#clock').text(h + ":" + m); + + // defined in the template this is loaded into + // eslint-disable-next-line no-undef + 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; + + var bgNum = parseFloat(rec.sgv); + + // 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'); + } + + // Insert the delta value text. + $('#delta').html(deltaDisplayValue); + + // Show delta + if (window.serverSettings.settings.showClockDelta) { + $('#delta').css('display', 'inline-block'); + } + + // Threshold background coloring. + if (bgNum < bgLow) { + $('body').css('background-color', red); + } + if ((bgLow <= bgNum) && (bgNum < bgTargetBottom)) { + $('body').css('background-color', blue); + } + if ((bgTargetBottom <= bgNum) && (bgNum < bgTargetTop)) { + $('body').css('background-color', green); + } + if ((bgTargetTop <= bgNum) && (bgNum < bgHigh)) { + $('body').css('background-color', yellow); + } + if (bgNum >= bgHigh) { + $('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%)'); + + if (!window.serverSettings.settings.showClockLastTime) { + $('#staleTime').css('display', 'block'); + } + + } else { + $('body').css('color', 'white'); + $('#arrow').css('filter', 'brightness(100%)'); + + if (!window.serverSettings.settings.showClockLastTime) { + $('#staleTime').css('display', 'none'); + } + + } + } +}; + +client.init = function init () { + console.log('init'); + client.query(); + setInterval(client.query, 1 * 60 * 1000); +}; + +module.exports = client; diff --git a/lib/client/index.js b/lib/client/index.js index 7a305148677..469009a9a94 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -1,5 +1,4 @@ 'use strict'; -'use strict'; var _ = require('lodash'); var $ = (global && global.$) || require('jquery'); @@ -16,13 +15,13 @@ var levels = require('../levels'); var times = require('../times'); var receiveDData = require('./receiveddata'); -var client = { }; +var client = {}; $('#loadingMessageText').html('Connecting to server'); client.hashauth = require('../hashauth').init(client, $); -client.headers = function headers ( ) { +client.headers = function headers () { if (client.authorized) { return { Authorization: 'Bearer ' + client.authorized.token @@ -32,11 +31,16 @@ client.headers = function headers ( ) { 'api-secret': client.hashauth.hash() }; } else { - return { }; + return {}; } }; -client.init = function init(callback) { +client.crashed = function crashed () { + $('#centerMessagePanel').show(); + $('#loadingMessageText').html('It appears the server has crashed. Please go to Heroku or Azure and reboot the server.'); +} + +client.init = function init (callback) { client.browserUtils = require('./browser-utils')($); @@ -60,50 +64,61 @@ client.init = function init(callback) { console.log('Application appears to be online'); $('#centerMessagePanel').hide(); client.load(serverSettings, callback); - }).fail(function fail(jqXHR, textStatus, errorThrown) { + // eslint-disable-next-line no-unused-vars + }).fail(function fail (jqXHR, textStatus, errorThrown) { - // check if we couldn't reach the server at all, show offline message - if (jqXHR.readyState == 0) { + // check if we couldn't reach the server at all, show offline message + if (!jqXHR.readyState) { console.log('Application appears to be OFFLINE'); $('#loadingMessageText').html('Connecting to Nightscout server failed, retrying every 2 seconds'); window.setTimeout(window.Nightscout.client.init(), 2000); return; - } - + } + //no server setting available, use defaults, auth, etc if (client.settingsFailed) { console.log('Already tried to get settings after auth, but failed'); } else { client.settingsFailed = true; - language.set('en'); + + // detect browser language + var lang = Storages.localStorage.get('language') || (navigator.language || navigator.userLanguage).toLowerCase(); + if (lang !== 'zh_cn' && lang !== 'zh-cn' && lang !== 'zh_tw' && lang !== 'zh-tw') { + lang = lang.substring(0, 2); + } else { + lang = lang.replace('-', '_'); + } + if (language.languages.find(l => l.code === lang)) { + language.set(lang); + } else { + language.set('en'); + } + client.translate = language.translate; - // auth failed, hide loader and request for key - $('#centerMessagePanel').hide(); - client.hashauth.requestAuthentication(function afterRequest ( ) { - client.init(null, callback); + // auth failed, hide loader and request for key + $('#centerMessagePanel').hide(); + client.hashauth.requestAuthentication(function afterRequest () { + client.init(callback); }); } }); }; -client.load = function load(serverSettings, callback) { +client.load = function load (serverSettings, callback) { - var UPDATE_TRANS_MS = 750 // milliseconds - , FORMAT_TIME_12 = '%-I:%M %p' + var FORMAT_TIME_12 = '%-I:%M %p' , FORMAT_TIME_12_COMPACT = '%-I:%M' , FORMAT_TIME_24 = '%H:%M%' , FORMAT_TIME_12_SCALE = '%-I %p' - , FORMAT_TIME_24_SCALE = '%H' - ; + , FORMAT_TIME_24_SCALE = '%H'; var history = 48; - + var chart , socket , isInitialData = false - , prevSGV - , opacity = {current: 1, DAY: 1, NIGHT: 0.5} + , opacity = { current: 1, DAY: 1, NIGHT: 0.5 } , clientAlarms = {} , alarmInProgress = false , alarmMessage @@ -111,14 +126,17 @@ client.load = function load(serverSettings, callback) { , currentAnnouncement , alarmSound = 'alarm.mp3' , urgentAlarmSound = 'alarm2.mp3' - , previousNotifyTimestamp - ; + , previousNotifyTimestamp; - client.entryToDate = function entryToDate (entry) { return new Date(entry.mills); }; + client.entryToDate = function entryToDate (entry) { + if (entry.date) return entry.date; + entry.date = new Date(entry.mills); + return entry.date; + }; client.now = Date.now(); client.ddata = require('../data/ddata')(); - client.forecastTime = times.mins(30).msecs; + client.forecastTime = client.defaultForecastTime = times.mins(30).msecs; client.entries = []; client.ticks = require('./ticks'); @@ -130,8 +148,7 @@ client.load = function load(serverSettings, callback) { , minorPills = $('.bgStatus .minorPills') , statusPills = $('.status .statusPills') , primary = $('.primary') - , editButton = $('#editbutton') - ; + , editButton = $('#editbutton'); client.tooltip = d3.select('body').append('div') .attr('class', 'tooltip') @@ -146,9 +163,12 @@ client.load = function load(serverSettings, callback) { client.plugins = require('../plugins/')({ settings: client.settings + , extendedSettings: client.settings.extendedSettings , language: language }).registerClientDefaults(); + browserSettings.loadPluginSettings(client); + client.utils = require('../utils')({ settings: client.settings , language: language @@ -236,7 +256,7 @@ client.load = function load(serverSettings, callback) { client.boluscalc = require('./boluscalc')(client, $); client.profilefunctions = profile; - + client.editMode = false; //TODO: use the bus for updates and notifications @@ -248,13 +268,15 @@ client.load = function load(serverSettings, callback) { //start the bus after setting up listeners //client.ctx.bus.uptime( ); - client.dataExtent = function dataExtent ( ) { - return client.entries.length > 0 ? - d3.extent(client.entries, client.entryToDate) - : d3.extent([new Date(client.now - times.hours(history).msecs), new Date(client.now)]); + client.dataExtent = function dataExtent () { + if (client.entries.length > 0) { + return [client.entryToDate(client.entries[0]), client.entryToDate(client.entries[client.entries.length - 1])]; + } else { + return [new Date(client.now - times.hours(history).msecs), new Date(client.now)]; + } }; - client.bottomOfPills = function bottomOfPills ( ) { + client.bottomOfPills = function bottomOfPills () { //the offset's might not exist for some tests var bottomOfPrimary = primary.offset() ? primary.offset().top + primary.height() : 0; var bottomOfMinorPills = minorPills.offset() ? minorPills.offset().top + minorPills.height() : 0; @@ -262,16 +284,16 @@ client.load = function load(serverSettings, callback) { return Math.max(bottomOfPrimary, bottomOfMinorPills, bottomOfStatusPills); }; - function formatTime(time, compact) { + function formatTime (time, compact) { var timeFormat = getTimeFormat(false, compact); - time = d3.time.format(timeFormat)(time); + time = d3.timeFormat(timeFormat)(time); if (client.settings.timeFormat !== 24) { time = time.toLowerCase(); } return time; } - function getTimeFormat(isForScale, compact) { + function getTimeFormat (isForScale, compact) { var timeFormat = FORMAT_TIME_12; if (client.settings.timeFormat === 24) { timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; @@ -283,7 +305,7 @@ client.load = function load(serverSettings, callback) { } //TODO: replace with utils.scaleMgdl and/or utils.roundBGForDisplay - function scaleBg(bg) { + function scaleBg (bg) { if (client.settings.units === 'mmol') { return units.mgdlToMMOL(bg); } else { @@ -291,8 +313,8 @@ client.load = function load(serverSettings, callback) { } } - function generateTitle ( ) { - function s(value, sep) { return value ? value + ' ' : sep || ''; } + function generateTitle () { + function s (value, sep) { return value ? value + ' ' : sep || ''; } var title = ''; @@ -300,7 +322,7 @@ client.load = function load(serverSettings, callback) { if (status !== 'current') { var ago = client.timeago.calcDisplay(client.sbx.lastSGVEntry(), client.sbx.time); - title = s(ago.value) + s(ago.label, ' - ') + title; + title = s(ago.value) + s(ago.label, ' - ') + title; } else if (client.latestSGV) { var currentMgdl = client.latestSGV.mgdl; @@ -317,12 +339,12 @@ client.load = function load(serverSettings, callback) { return title; } - function resetCustomTitle ( ) { + function resetCustomTitle () { var customTitle = client.settings.customTitle || 'Nightscout'; $('.customTitle').text(customTitle); } - function checkAnnouncement() { + function checkAnnouncement () { var result = { inProgress: currentAnnouncement ? Date.now() - currentAnnouncement.received < times.mins(5).msecs : false }; @@ -339,19 +361,19 @@ client.load = function load(serverSettings, callback) { return result; } - function updateTitle ( ) { + function updateTitle () { var windowTitle; var announcementStatus = checkAnnouncement(); if (alarmMessage && alarmInProgress) { $('.customTitle').text(alarmMessage); - if (!isTimeAgoAlarmType( )) { + if (!isTimeAgoAlarmType()) { windowTitle = alarmMessage + ': ' + generateTitle(); } } else if (announcementStatus.inProgress && announcementStatus.message) { windowTitle = announcementStatus.message + ': ' + generateTitle(); - } else { + } else { resetCustomTitle(); } @@ -360,50 +382,64 @@ client.load = function load(serverSettings, callback) { $(document).attr('title', windowTitle || generateTitle()); } -// clears the current user brush and resets to the current real time data - function updateBrushToNow(skipBrushing) { - - // get current time range - var dataRange = client.dataExtent(); + // clears the current user brush and resets to the current real time data + function updateBrushToNow (skipBrushing) { // update brush and focus chart with recent data - d3.select('.brush') - .transition() - .duration(UPDATE_TRANS_MS) - .call(chart.brush.extent([new Date(dataRange[1].getTime() - client.focusRangeMS), dataRange[1]])); + var brushExtent = client.dataExtent(); + + brushExtent[0] = new Date(brushExtent[1].getTime() - client.focusRangeMS); + + // console.log('Resetting brush in updateBrushToNow: ', brushExtent); + + chart.theBrush && chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); if (!skipBrushing) { brushed(); } } - function alarmingNow() { + function alarmingNow () { return container.hasClass('alarming'); } - function inRetroMode() { + function inRetroMode () { return chart && chart.inRetroMode(); } - function brushed ( ) { + function brushed () { + // Brush not initialized + console.log("brushed"); + if (!chart.theBrush) { + return; + } + + // default to most recent focus period + var brushExtent = client.dataExtent(); + brushExtent[0] = new Date(brushExtent[1].getTime() - client.focusRangeMS); - var brushExtent = chart.brush.extent(); + var brushedRange = d3.brushSelection(chart.theBrush.node()); - // ensure that brush extent is fixed at 3.5 hours - if (brushExtent[1].getTime() - brushExtent[0].getTime() !== client.focusRangeMS) { + if (brushedRange) { + brushExtent = brushedRange.map(chart.xScale2.invert); + } + + // console.log('Brushed to: ', brushExtent); + + if (!brushedRange || (brushExtent[1].getTime() - brushExtent[0].getTime() !== client.focusRangeMS)) { // ensure that brush updating is with the time range if (brushExtent[0].getTime() + client.focusRangeMS > client.dataExtent()[1].getTime()) { brushExtent[0] = new Date(brushExtent[1].getTime() - client.focusRangeMS); - d3.select('.brush') - .call(chart.brush.extent([brushExtent[0], brushExtent[1]])); } else { brushExtent[1] = new Date(brushExtent[0].getTime() + client.focusRangeMS); - d3.select('.brush') - .call(chart.brush.extent([brushExtent[0], brushExtent[1]])); } + + // console.log('Updating brushed to: ', brushExtent); + + chart.theBrush.call(chart.brush.move, brushExtent.map(chart.xScale2)); } - function adjustCurrentSGVClasses(value, isCurrent) { + function adjustCurrentSGVClasses (value, isCurrent) { var reallyCurrentAndNotAlarming = isCurrent && !inRetroMode() && !alarmingNow(); bgStatus.toggleClass('current', alarmingNow() || reallyCurrentAndNotAlarming); @@ -416,7 +452,6 @@ client.load = function load(serverSettings, callback) { currentBG.toggleClass('icon-hourglass', value === 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value === 39 || value > 400); - container.removeClass('loading'); } function updateCurrentSGV (entry) { @@ -438,6 +473,20 @@ client.load = function load(serverSettings, callback) { adjustCurrentSGVClasses(value, isCurrent); } + function mergeDeviceStatus (retro, ddata) { + if (!retro) { + return ddata; + } + + var result = retro.map(x => Object.assign(x, ddata.find(y => y._id == x._id))); + + var missingInRetro = ddata.filter(y => !retro.find(x => x._id == y._id)); + + result.push(...missingInRetro); + + return result; + } + function updatePlugins (time) { //TODO: doing a clone was slow, but ok to let plugins muck with data? @@ -446,10 +495,22 @@ client.load = function load(serverSettings, callback) { client.ddata.inRetroMode = inRetroMode(); client.ddata.profile = profile; + // retro data only ever contains device statuses + // Cleate a clone of the data for the sandbox given to plugins + + var mergedStatuses = client.ddata.devicestatus; + + if (client.retro.data) { + mergedStatuses = mergeDeviceStatus(client.retro.data.devicestatus, client.ddata.devicestatus); + } + + var clonedData = _.clone(client.ddata); + clonedData.devicestatus = mergedStatuses; + client.sbx = sandbox.clientInit( client.ctx , new Date(time).getTime() //make sure we send a timestamp - , _.merge({}, client.retro.data || {}, client.ddata) + , clonedData ); //all enabled plugins get a chance to set properties, even if they aren't shown @@ -469,7 +530,7 @@ client.load = function load(serverSettings, callback) { forecastOption.append(forecastLabel); forecastLabel.append(forecastCheckbox); forecastLabel.append('Show ' + info.label + ''); - forecastCheckbox.change(function onChange(event) { + forecastCheckbox.change(function onChange (event) { var checkbox = $(event.target); var type = checkbox.attr('data-forecast-type'); var checked = checkbox.prop('checked'); @@ -477,7 +538,7 @@ client.load = function load(serverSettings, callback) { client.settings.showForecast += ' ' + type; } else { client.settings.showForecast = _.chain(client.settings.showForecast.split(' ')) - .filter(function (forecast) { return forecast !== type; }) + .filter(function(forecast) { return forecast !== type; }) .value() .join(' '); } @@ -491,9 +552,9 @@ client.load = function load(serverSettings, callback) { client.boluscalc.updateVisualisations(client.sbx); } - function clearCurrentSGV ( ) { + function clearCurrentSGV () { currentBG.text('---'); - container.removeClass('urgent warning inrange'); + container.removeClass('alarming urgent warning inrange'); } var nowDate = null; @@ -502,7 +563,7 @@ client.load = function load(serverSettings, callback) { }); var focusPoint = _.last(nowData); - function updateHeader() { + function updateHeader () { if (inRetroMode()) { nowDate = brushExtent[1]; $('#currentTime') @@ -533,10 +594,11 @@ client.load = function load(serverSettings, callback) { } var top = (client.bottomOfPills() + 5); - $('#chartContainer').css({top: top + 'px', height: $(window).height() - top - 10}); + $('#chartContainer').css({ top: top + 'px', height: $(window).height() - top - 10 }); + container.removeClass('loading'); } - function sgvToColor(sgv) { + function sgvToColor (sgv) { var color = 'grey'; if (client.settings.theme !== 'default') { @@ -556,7 +618,7 @@ client.load = function load(serverSettings, callback) { return color; } - function sgvToColoredRange(sgv) { + function sgvToColoredRange (sgv) { var range = ''; if (client.settings.theme !== 'default') { @@ -576,7 +638,7 @@ client.load = function load(serverSettings, callback) { return range; } - function formatAlarmMessage(notify) { + function formatAlarmMessage (notify) { var announcementMessage = notify && notify.isAnnouncement && notify.message && notify.message.length > 1; if (announcementMessage) { @@ -584,7 +646,7 @@ client.load = function load(serverSettings, callback) { } else if (notify) { return notify.title; } - return null; + return null; } function setAlarmMessage (notify) { @@ -599,7 +661,7 @@ client.load = function load(serverSettings, callback) { var selector = '.audio.alarms audio.' + file; if (!alarmingNow()) { - d3.select(selector).each(function () { + d3.select(selector).each(function() { var audio = this; playAlarm(audio); $(this).addClass('playing'); @@ -628,7 +690,7 @@ client.load = function load(serverSettings, callback) { event.preventDefault(); } - function playAlarm(audio) { + function playAlarm (audio) { // ?mute=true disables alarms to testers. if (client.browserUtils.queryParms().mute !== 'true') { audio.play(); @@ -637,11 +699,11 @@ client.load = function load(serverSettings, callback) { } } - function stopAlarm(isClient, silenceTime, notify) { + function stopAlarm (isClient, silenceTime, notify) { alarmInProgress = false; alarmMessage = null; container.removeClass('urgent warning'); - d3.selectAll('audio.playing').each(function () { + d3.selectAll('audio.playing').each(function() { var audio = this; audio.pause(); $(this).removeClass('playing'); @@ -688,7 +750,7 @@ client.load = function load(serverSettings, callback) { brushed(); } - function refreshAuthIfNeeded ( ) { + function refreshAuthIfNeeded () { var token = client.browserUtils.queryParms().token; if (token && client.authorized) { var renewTime = (client.authorized.exp * 1000) - times.mins(15).msecs - Math.abs((client.authorized.iat * 1000) - client.authorized.lat); @@ -696,7 +758,7 @@ client.load = function load(serverSettings, callback) { if (client.now > renewTime) { console.info('Refreshing authorization'); $.ajax('/api/v2/authorization/request/' + token, { - success: function (authorized) { + success: function(authorized) { if (authorized) { console.info('Got new authorization', authorized); authorized.lat = client.now; @@ -710,10 +772,10 @@ client.load = function load(serverSettings, callback) { } } - function updateClock() { + function updateClock () { updateClockDisplay(); var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; - setTimeout(updateClock,interval); + setTimeout(updateClock, interval); updateTimeAgo(); if (chart) { @@ -735,7 +797,7 @@ client.load = function load(serverSettings, callback) { } } - function updateClockDisplay() { + function updateClockDisplay () { if (inRetroMode()) { return; } @@ -743,7 +805,7 @@ client.load = function load(serverSettings, callback) { $('#currentTime').text(formatTime(new Date(client.now), true)).css('text-decoration', ''); } - function getClientAlarm(level, group) { + function getClientAlarm (level, group) { var key = level + '-' + group; var alarm = clientAlarms[key]; if (!alarm) { @@ -753,13 +815,13 @@ client.load = function load(serverSettings, callback) { return alarm; } - function isTimeAgoAlarmType() { + function isTimeAgoAlarmType () { return currentNotify && currentNotify.group === 'Time Ago'; } function isStale (status) { - return client.settings.alarmTimeagoWarn && status === 'warn' - || client.settings.alarmTimeagoUrgent && status === 'urgent'; + return client.settings.alarmTimeagoWarn && status === 'warn' || + client.settings.alarmTimeagoUrgent && status === 'urgent'; } function notAcked (alarm) { @@ -786,12 +848,18 @@ client.load = function load(serverSettings, callback) { container.toggleClass('alarming-timeago', status !== 'current'); - if (alarmingNow() && status === 'current' && isTimeAgoAlarmType( )) { + if (status === 'warn') { + container.addClass('warn'); + } else if (status === 'urgent') { + container.addClass('urgent'); + } + + if (alarmingNow() && status === 'current' && isTimeAgoAlarmType()) { stopAlarm(true, times.min().msecs); } } - function updateTimeAgo() { + function updateTimeAgo () { var status = client.timeago.checkStatus(client.sbx); if (status !== 'current') { updateTitle(); @@ -799,20 +867,20 @@ client.load = function load(serverSettings, callback) { checkTimeAgoAlarm(status); } - function updateTimeAgoSoon() { - setTimeout(function updatingTimeAgoNow() { + function updateTimeAgoSoon () { + setTimeout(function updatingTimeAgoNow () { updateTimeAgo(); }, times.secs(10).msecs); } - function refreshChart(updateToNow) { + function refreshChart (updateToNow) { if (updateToNow) { updateBrushToNow(); } chart.update(false); } - (function watchVisibility ( ) { + (function watchVisibility () { // Set the name of the hidden property and the change event for visibility var hidden, visibilityChange; if (typeof document.hidden !== 'undefined') { @@ -829,7 +897,7 @@ client.load = function load(serverSettings, callback) { visibilityChange = 'webkitvisibilitychange'; } - document.addEventListener(visibilityChange, function visibilityChanged ( ) { + document.addEventListener(visibilityChange, function visibilityChanged () { var prevHidden = client.documentHidden; client.documentHidden = document[hidden]; @@ -845,7 +913,7 @@ client.load = function load(serverSettings, callback) { updateClock(); updateTimeAgoSoon(); - function Dropdown(el) { + function Dropdown (el) { this.ddmenuitem = 0; this.$el = $(el); @@ -853,13 +921,13 @@ client.load = function load(serverSettings, callback) { $(document).click(function() { that.close(); }); } - Dropdown.prototype.close = function () { + Dropdown.prototype.close = function() { if (this.ddmenuitem) { this.ddmenuitem.css('visibility', 'hidden'); this.ddmenuitem = 0; } }; - Dropdown.prototype.open = function (e) { + Dropdown.prototype.open = function(e) { this.close(); this.ddmenuitem = $(this.$el).css('visibility', 'visible'); e.stopPropagation(); @@ -868,7 +936,7 @@ client.load = function load(serverSettings, callback) { var silenceDropdown = new Dropdown('#silenceBtn'); var viewDropdown = new Dropdown('#viewMenu'); - $('.bgButton').click(function (e) { + $('.bgButton').click(function(e) { if (alarmingNow()) { silenceDropdown.open(e); } @@ -887,23 +955,23 @@ client.load = function load(serverSettings, callback) { viewDropdown.open(e); } }); - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Client-side code to connect to server and handle incoming data //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // eslint-disable-next-line no-undef client.socket = socket = io.connect(); socket.on('dataUpdate', dataUpdate); - function resetRetro ( ) { + function resetRetro () { client.retro = { loadedMills: 0 , loadStartedMills: 0 }; } - client.resetRetroIfNeeded = function resetRetroIfNeeded ( ) { + client.resetRetroIfNeeded = function resetRetroIfNeeded () { if (client.retro.loadedMills > 0 && Date.now() - client.retro.loadedMills > times.mins(5).msecs) { resetRetro(); console.info('Cleared retro data to free memory'); @@ -912,7 +980,7 @@ client.load = function load(serverSettings, callback) { resetRetro(); - client.loadRetroIfNeeded = function loadRetroIfNeeded ( ) { + client.loadRetroIfNeeded = function loadRetroIfNeeded () { var now = Date.now(); if (now - client.retro.loadStartedMills < times.secs(30).msecs) { console.info('retro already loading, started', new Date(client.retro.loadStartedMills)); @@ -940,20 +1008,32 @@ client.load = function load(serverSettings, callback) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Alarms and Text handling //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket.on('connect', function () { - console.log('Client connected to server.'); + + + client.authorizeSocket = function authorizeSocket() { + + console.log('Authorizing socket'); + var auth_data = { + client: 'web' + , secret: client.authorized && client.authorized.token ? null : client.hashauth.hash() + , token: client.authorized && client.authorized.token + , history: history + }; + socket.emit( 'authorize' - , { - client: 'web' - , secret: client.authorized && client.authorized.token ? null : client.hashauth.hash() - , token: client.authorized && client.authorized.token - , history: history - } - , function authCallback(data) { - console.log('Client rights: ',data); + , auth_data + , function authCallback (data) { + + console.log('Socket auth response', data); + + if (!data) { + console.log('Crashed!'); + client.crashed(); + } + if (!data.read || !hasRequiredPermission()) { - client.hashauth.requestAuthentication(function afterRequest ( ) { + client.hashauth.requestAuthentication(function afterRequest () { client.hashauth.updateSocketAuth(); if (callback) { callback(); @@ -964,9 +1044,14 @@ client.load = function load(serverSettings, callback) { } } ); + } + + socket.on('connect', function() { + console.log('Client connected to server.'); + client.authorizeSocket(); }); - function hasRequiredPermission ( ) { + function hasRequiredPermission () { if (client.requiredPermission) { if (client.hashauth && client.hashauth.isAuthenticated()) { return true; @@ -980,39 +1065,38 @@ client.load = function load(serverSettings, callback) { //with predicted alarms, latestSGV may still be in target so to see if the alarm // is for a HIGH we can only check if it's >= the bottom of the target - function isAlarmForHigh() { + function isAlarmForHigh () { return client.latestSGV && client.latestSGV.mgdl >= client.settings.thresholds.bgTargetBottom; } //with predicted alarms, latestSGV may still be in target so to see if the alarm // is for a LOW we can only check if it's <= the top of the target - function isAlarmForLow() { + function isAlarmForLow () { return client.latestSGV && client.latestSGV.mgdl <= client.settings.thresholds.bgTargetTop; } - socket.on('notification', function (notify) { - console.log('notification from server:',notify); - - if (notify.timestamp && previousNotifyTimestamp != notify.timestamp) - { - previousNotifyTimestamp = notify.timestamp; + socket.on('notification', function(notify) { + console.log('notification from server:', notify); + + if (notify.timestamp && previousNotifyTimestamp !== notify.timestamp) { + previousNotifyTimestamp = notify.timestamp; client.plugins.visualizeAlarm(client.sbx, notify, notify.title + ' ' + notify.message); } else { console.log('No timestamp found for notify, not passing to plugins'); } }); - socket.on('announcement', function (notify) { + socket.on('announcement', function(notify) { console.info('announcement received from server'); - console.log('notify:',notify); + console.log('notify:', notify); currentAnnouncement = notify; currentAnnouncement.received = Date.now(); updateTitle(); }); - socket.on('alarm', function (notify) { + socket.on('alarm', function(notify) { console.info('alarm received from server'); - console.log('notify:',notify); + console.log('notify:', notify); var enabled = (isAlarmForHigh() && client.settings.alarmHigh) || (isAlarmForLow() && client.settings.alarmLow); if (enabled) { console.log('Alarm raised!'); @@ -1023,9 +1107,9 @@ client.load = function load(serverSettings, callback) { chart.update(false); }); - socket.on('urgent_alarm', function (notify) { + socket.on('urgent_alarm', function(notify) { console.info('urgent alarm received from server'); - console.log('notify:',notify); + console.log('notify:', notify); var enabled = (isAlarmForHigh() && client.settings.alarmUrgentHigh) || (isAlarmForLow() && client.settings.alarmUrgentLow); if (enabled) { @@ -1037,7 +1121,7 @@ client.load = function load(serverSettings, callback) { chart.update(false); }); - socket.on('clear_alarm', function (notify) { + socket.on('clear_alarm', function(notify) { console.info('got clear_alarm', notify); if (alarmInProgress) { console.log('clearing alarm'); @@ -1046,7 +1130,15 @@ client.load = function load(serverSettings, callback) { }); $('#testAlarms').click(function(event) { - d3.selectAll('.audio.alarms audio').each(function () { + + // Speech synthesis also requires on iOS that user triggers a speech event for it to speak anything + if (client.plugins('speech').isEnabled) { + var msg = new SpeechSynthesisUtterance('Ok ok.'); + msg.lang = 'en-US'; + window.speechSynthesis.speak(msg); + } + + d3.selectAll('.audio.alarms audio').each(function() { var audio = this; playAlarm(audio); setTimeout(function() { @@ -1069,32 +1161,32 @@ client.load = function load(serverSettings, callback) { $('.foodcontrol').toggle(client.settings.enable.indexOf('food') > -1); // hide cob control if not enabled $('.cobcontrol').toggle(client.settings.enable.indexOf('cob') > -1); - // hide profile controls if not enabled - $('.profilecontrol').toggle(client.settings.enable.indexOf('profile') > -1); container.toggleClass('has-minor-pills', client.plugins.hasShownType('pill-minor', client.settings)); - function prepareEntries ( ) { + function prepareEntries () { // Post processing after data is in - var temp1 = [ ]; - if (client.ddata.cal && client.rawbg.isEnabled(client.sbx)) { - temp1 = client.ddata.sgvs.map(function (entry) { - var rawbgValue = client.rawbg.showRawBGs(entry.mgdl, entry.noise, client.ddata.cal, client.sbx) ? client.rawbg.calc(entry, client.ddata.cal, client.sbx) : 0; + var temp1 = []; + var sbx = client.sbx.withExtendedSettings(client.rawbg); + + if (client.ddata.cal && client.rawbg.isEnabled(sbx)) { + temp1 = client.ddata.sgvs.map(function(entry) { + var rawbgValue = client.rawbg.showRawBGs(entry.mgdl, entry.noise, client.ddata.cal, sbx) ? client.rawbg.calc(entry, client.ddata.cal, sbx) : 0; if (rawbgValue > 0) { return { mills: entry.mills - 2000, mgdl: rawbgValue, color: 'white', type: 'rawbg' }; } else { return null; } - }).filter(function (entry) { + }).filter(function(entry) { return entry !== null; }); } - var temp2 = client.ddata.sgvs.map(function (obj) { - return { mills: obj.mills, mgdl: obj.mgdl, direction: obj.direction, color: sgvToColor(obj.mgdl), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; + var temp2 = client.ddata.sgvs.map(function(obj) { + return { mills: obj.mills, mgdl: obj.mgdl, direction: obj.direction, color: sgvToColor(obj.mgdl), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered }; }); client.entries = []; client.entries = client.entries.concat(temp1, temp2); - client.entries = client.entries.concat(client.ddata.mbgs.map(function (obj) { + client.entries = client.entries.concat(client.ddata.mbgs.map(function(obj) { return { mills: obj.mills, mgdl: obj.mgdl, color: 'red', type: 'mbg', device: obj.device }; })); @@ -1103,14 +1195,19 @@ client.load = function load(serverSettings, callback) { return entry.mills > tooOld; }); - client.entries.forEach(function (point) { + client.entries.forEach(function(point) { if (point.mgdl < 39) { point.color = 'transparent'; } }); + + client.entries.sort(function sorter (a, b) { + return a.mills - b.mills; + }); } - function dataUpdate (received) { + function dataUpdate (received, headless) { + console.info('got dataUpdate', new Date(client.now)); var lastUpdated = Date.now(); receiveDData(received, client.ddata, client.settings); @@ -1123,10 +1220,9 @@ client.load = function load(serverSettings, callback) { } if (client.ddata.sgvs) { - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + // TODO change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) client.ctx.data.lastUpdated = lastUpdated; client.latestSGV = client.ddata.sgvs[client.ddata.sgvs.length - 1]; - prevSGV = client.ddata.sgvs[client.ddata.sgvs.length - 2]; } client.ddata.inRetroMode = false; @@ -1144,18 +1240,23 @@ client.load = function load(serverSettings, callback) { prepareEntries(); updateTitle(); + // Don't invoke D3 in headless mode + + if (headless) return; + if (!isInitialData) { isInitialData = true; chart = client.chart = require('./chart')(client, d3, $); - brushed(); chart.update(true); + brushed(); + chart.update(false); } else if (!inRetroMode()) { + brushed(); chart.update(false); - client.plugins.updateVisualisations(client.nowSBX); + } else { + chart.updateContext(); } - } - }; module.exports = client; diff --git a/lib/client/receiveddata.js b/lib/client/receiveddata.js index be622a7705f..e0adf13fb3b 100644 --- a/lib/client/receiveddata.js +++ b/lib/client/receiveddata.js @@ -4,22 +4,22 @@ var _ = require('lodash'); var TWO_DAYS = 172800000; -function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray, maxAge) { +function mergeDataUpdate (isDelta, cachedDataArray, receivedDataArray, maxAge) { - function nsArrayDiff(oldArray, newArray) { - var seen = {}; + function nsArrayDiff (oldArray, newArray) { + var seen = []; var l = oldArray.length; - + for (var i = 0; i < l; i++) { - if (oldArray[i] !== null) { - seen[oldArray[i].mills] = true; + if (oldArray[i] !== null) { + seen.push(oldArray[i].mills); } } - + var result = []; l = newArray.length; for (var j = 0; j < l; j++) { - if (!seen.hasOwnProperty(newArray[j].mills)) { + if (!seen.includes(newArray[j].mills)) { result.push(newArray[j]); //console.log('delta data found'); } } @@ -39,11 +39,11 @@ function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray, maxAge) { // purge old data from cache before updating var mAge = (isNaN(maxAge) || maxAge == null) ? TWO_DAYS : maxAge; var twoDaysAgo = new Date().getTime() - mAge; - + for (var i = 0; i < cachedDataArray.length; i++) { var element = cachedDataArray[i]; - if (element !== null && element !== undefined && element.mills <= twoDaysAgo) { - cachedDataArray.splice(i,0); + if (element !== null && element !== undefined && element.mills <= twoDaysAgo) { + cachedDataArray.splice(i, 0); } } @@ -54,7 +54,7 @@ function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray, maxAge) { }); } -function mergeTreatmentUpdate(isDelta, cachedDataArray, receivedDataArray) { +function mergeTreatmentUpdate (isDelta, cachedDataArray, receivedDataArray) { // If there was no delta data, just return the original data if (!receivedDataArray) { @@ -78,12 +78,12 @@ function mergeTreatmentUpdate(isDelta, cachedDataArray, receivedDataArray) { for (var j = 0; j < m; j++) { if (no._id === cachedDataArray[j]._id) { if (no.action === 'remove') { - cachedDataArray.splice(j,1); + cachedDataArray.splice(j, 1); break; } if (no.action === 'update') { delete no.action; - cachedDataArray.splice(j,1,no); + cachedDataArray.splice(j, 1, no); break; } } diff --git a/lib/client/renderer.js b/lib/client/renderer.js index 6dc0f68fcb3..f058d3de860 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -2,27 +2,35 @@ var _ = require('lodash'); var times = require('../times'); +var consts = require('../constants'); var DEFAULT_FOCUS = times.hours(3).msecs , WIDTH_SMALL_DOTS = 420 , WIDTH_BIG_DOTS = 800 - , TOOLTIP_TRANS_MS = 100 // milliseconds , TOOLTIP_WIDTH = 150 //min-width + padding - ; +; + +const zeroDate = new Date(0); function init (client, d3) { - var renderer = { }; + var renderer = {}; var utils = client.utils; var translate = client.translate; + function getOrAddDate(entry) { + if (entry.date) return entry.date; + entry.date = new Date(entry.mills); + return entry.date; + } + //chart isn't created till the client gets data, so can grab the var at init - function chart() { + function chart () { return client.chart; } - function focusRangeAdjustment ( ) { + function focusRangeAdjustment () { return client.focusRangeMS === DEFAULT_FOCUS ? 1 : 1 + ((client.focusRangeMS - DEFAULT_FOCUS) / DEFAULT_FOCUS / 8); } @@ -39,66 +47,50 @@ function init (client, d3) { return radius / focusRangeAdjustment(); }; - function tooltipLeft ( ) { - var windowWidth = $(client.tooltip).parent().parent().width(); + function tooltipLeft () { + var windowWidth = $(client.tooltip.node()).parent().parent().width(); var left = d3.event.pageX + TOOLTIP_WIDTH < windowWidth ? d3.event.pageX : windowWidth - TOOLTIP_WIDTH - 10; return left + 'px'; } - function hideTooltip ( ) { - client.tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); + function hideTooltip () { + client.tooltip.style('opacity', 0); } // get the desired opacity for context chart based on the brush extent - renderer.highlightBrushPoints = function highlightBrushPoints(data) { - if (data.mills >= chart().brush.extent()[0].getTime() && data.mills <= chart().brush.extent()[1].getTime()) { + renderer.highlightBrushPoints = function highlightBrushPoints (data) { + var selectedRange = chart().createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); + + if (client.latestSGV && data.mills >= from && data.mills <= to) { return chart().futureOpacity(data.mills - client.latestSGV.mills); } else { return 0.5; } }; - renderer.bubbleScale = function bubbleScale ( ) { + renderer.bubbleScale = function bubbleScale () { // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) return (chart().prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (chart().prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment(); }; - renderer.addFocusCircles = function addFocusCircles ( ) { - // get slice of data so that concatenation of predictions do not interfere with subsequent updates - var focusData = client.entries.slice(); + renderer.addFocusCircles = function addFocusCircles () { - if (client.sbx.pluginBase.forecastPoints) { - var shownForecastPoints = _.filter(client.sbx.pluginBase.forecastPoints, function isShown(point) { - return client.settings.showForecast.indexOf(point.info.type) > -1; - }); - var maxForecastMills = _.max(_.map(shownForecastPoints, function (point) {return point.mills})); - // limit lookahead to the same as lookback - var focusHoursAheadMills = chart().brush.extent()[1].getTime() + client.focusRangeMS; - maxForecastMills = Math.min(focusHoursAheadMills, maxForecastMills); - client.forecastTime = maxForecastMills > 0 ? maxForecastMills - client.sbx.lastSGVMills() : 0; - focusData = focusData.concat(shownForecastPoints); - } - - // bind up the focus chart data to an array of circles - // selects all our data into data and uses date function to get current max date - var focusCircles = chart().focus.selectAll('circle').data(focusData, client.entryToDate); - - function prepareFocusCircles(sel) { + function updateFocusCircles (sel) { var badData = []; - sel.attr('cx', function (d) { - if (!d) { - console.error('Bad data', d); - return chart().xScale(new Date(0)); - } else if (!d.mills) { - console.error('Bad data, no mills', d); - return chart().xScale(new Date(0)); - } else { - return chart().xScale(new Date(d.mills)); - } - }) - .attr('cy', function (d) { + sel.attr('cx', function(d) { + if (!d) { + console.error('Bad data', d); + return chart().xScale(zeroDate); + } else if (!d.mills) { + console.error('Bad data, no mills', d); + return chart().xScale(zeroDate); + } else { + return chart().xScale(getOrAddDate(d)); + } + }) + .attr('cy', function(d) { var scaled = client.sbx.scaleEntry(d); if (isNaN(scaled)) { badData.push(d); @@ -107,19 +99,14 @@ function init (client, d3) { return chart().yScale(scaled); } }) - .attr('fill', function (d) { - return d.type === 'forecast' ? 'none' : d.color; - }) - .attr('opacity', function (d) { - return d.noFade ? 100 : chart().futureOpacity(d.mills - client.latestSGV.mills); - }) - .attr('stroke-width', function (d) { - return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 2 : 0; - }) - .attr('stroke', function (d) { - return (d.type === 'mbg' ? 'white' : d.color); + .attr('opacity', function(d) { + if (d.noFade) { + return null; + } else { + return !client.latestSGV ? 1 : chart().futureOpacity(d.mills - client.latestSGV.mills); + } }) - .attr('r', function (d) { + .attr('r', function(d) { return dotRadius(d.type); }); @@ -130,17 +117,33 @@ function init (client, d3) { return sel; } + function prepareFocusCircles (sel) { + updateFocusCircles(sel) + .attr('fill', function(d) { + return d.type === 'forecast' ? 'none' : d.color; + }) + .attr('stroke-width', function(d) { + return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 2 : 0; + }) + .attr('stroke', function(d) { + return (d.type === 'mbg' ? 'white' : d.color); + }); + + return sel; + } + function focusCircleTooltip (d) { if (d.type !== 'sgv' && d.type !== 'mbg' && d.type !== 'forecast') { return; } - function getRawbgInfo ( ) { - var info = { }; + function getRawbgInfo () { + var info = {}; + var sbx = client.sbx.withExtendedSettings(client.rawbg); if (d.type === 'sgv') { info.noise = client.rawbg.noiseCodeToDisplay(d.mgdl, d.noise); - if (client.rawbg.showRawBGs(d.mgdl, d.noise, client.ddata.cal, client.sbx)) { - info.value = utils.scaleMgdl(client.rawbg.calc(d, client.ddata.cal, client.sbx)); + if (client.rawbg.showRawBGs(d.mgdl, d.noise, client.ddata.cal, sbx)) { + info.value = utils.scaleMgdl(client.rawbg.calc(d, client.ddata.cal, sbx)); } } return info; @@ -148,46 +151,122 @@ function init (client, d3) { var rawbgInfo = getRawbgInfo(); - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - client.tooltip.html('' + translate('BG')+ ': ' + client.sbx.scaleEntry( d ) + - (d.type === 'mbg' ? '
' + translate('Device') + ': ' + d.device : '') + - (d.type === 'forecast' && d.forecastType ? '
' + translate('Forecast Type') + ': ' + d.forecastType : '') + - (rawbgInfo.value ? '
' + translate('Raw BG') + ': ' + rawbgInfo.value : '') + - (rawbgInfo.noise ? '
' + translate('Noise') + ': ' + rawbgInfo.noise : '') + - '
' + translate('Time') + ': ' + client.formatTime(new Date(d.mills))) + client.tooltip.style('opacity', .9); + client.tooltip.html('' + translate('BG') + ': ' + client.sbx.scaleEntry(d) + + (d.type === 'mbg' ? '
' + translate('Device') + ': ' + d.device : '') + + (d.type === 'forecast' && d.forecastType ? '
' + translate('Forecast Type') + ': ' + d.forecastType : '') + + (rawbgInfo.value ? '
' + translate('Raw BG') + ': ' + rawbgInfo.value : '') + + (rawbgInfo.noise ? '
' + translate('Noise') + ': ' + rawbgInfo.noise : '') + + '
' + translate('Time') + ': ' + client.formatTime(getOrAddDate(d))) .style('left', tooltipLeft()) .style('top', (d3.event.pageY + 15) + 'px'); } + // CGM data + + var focusData = client.entries; + + // bind up the focus chart data to an array of circles + // selects all our data into data and uses date function to get current max date + var focusCircles = chart().focus.selectAll('circle.entry-dot').data(focusData, function genKey (d) { + return "cgmreading." + d.mills; + }); + // if already existing then transition each circle to its new position - prepareFocusCircles(focusCircles.transition()); + updateFocusCircles(focusCircles); // if new circle then just display prepareFocusCircles(focusCircles.enter().append('circle')) + .attr('class', 'entry-dot') .on('mouseover', focusCircleTooltip) .on('mouseout', hideTooltip); focusCircles.exit().remove(); + + // Forecasts + + var shownForecastPoints = client.chart.getForecastData(); + + // bind up the focus chart data to an array of circles + // selects all our data into data and uses date function to get current max date + + var forecastCircles = chart().focus.selectAll('circle.forecast-dot').data(shownForecastPoints, function genKey (d) { + return d.forecastType + d.mills; + }); + + forecastCircles.exit().remove(); + + prepareFocusCircles(forecastCircles.enter().append('circle')) + .attr('class', 'forecast-dot') + .on('mouseover', focusCircleTooltip) + .on('mouseout', hideTooltip); + + updateFocusCircles(forecastCircles); + }; - renderer.addTreatmentCircles = function addTreatmentCircles ( ) { + renderer.addTreatmentCircles = function addTreatmentCircles (nowDate) { function treatmentTooltip (d) { - return ''+translate('Time')+': ' + client.formatTime(new Date(d.mills)) + '
' + - (d.eventType ? ''+translate('Treatment type')+': ' + translate(client.careportal.resolveEventName(d.eventType)) + '
' : '') + - (d.reason ? ''+translate('Reason')+': ' + translate(d.reason) + '
' : '') + - (d.glucose ? ''+translate('BG')+': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')': '') + '
' : '') + - (d.enteredBy ? ''+translate('Entered By')+': ' + d.enteredBy + '
' : '') + - (d.targetTop ? ''+translate('Target Top')+': ' + d.targetTop + '
' : '') + - (d.targetBottom ? ''+translate('Target Bottom')+': ' + d.targetBottom + '
' : '') + - (d.duration ? ''+translate('Duration')+': ' + Math.round(d.duration) + ' min
' : '') + - (d.notes ? ''+translate('Notes')+': ' + d.notes : ''); + var targetBottom = d.targetBottom; + var targetTop = d.targetTop; + + if (client.settings.units === 'mmol') { + targetBottom = Math.round(targetBottom / consts.MMOL_TO_MGDL * 10) / 10; + targetTop = Math.round(targetTop / consts.MMOL_TO_MGDL * 10) / 10; + } + + var correctionRangeText; + if (d.correctionRange) { + var min = d.correctionRange[0]; + var max = d.correctionRange[1]; + + if (client.settings.units === 'mmol') { + max = client.sbx.roundBGToDisplayFormat(client.sbx.scaleMgdl(max)); + min = client.sbx.roundBGToDisplayFormat(client.sbx.scaleMgdl(min)); + } + + if (d.correctionRange[0] === d.correctionRange[1]) { + correctionRangeText = '' + min; + } else { + correctionRangeText = '' + min + ' - ' + max; + } + } + + var durationText; + if (d.durationType === "indefinite") { + durationText = translate("Indefinite"); + } else if (d.duration) { + var durationMinutes = Math.round(d.duration); + if (durationMinutes > 0 && durationMinutes % 60 == 0) { + var durationHours = durationMinutes / 60; + if (durationHours > 1) { + durationText = durationHours + ' hours'; + } else { + durationText = durationHours + ' hour'; + } + } else { + durationText = durationMinutes + ' min'; + } + } + + return '' + translate('Time') + ': ' + client.formatTime(getOrAddDate(d)) + '
' + + (d.eventType ? '' + translate('Treatment type') + ': ' + translate(client.careportal.resolveEventName(d.eventType)) + '
' : '') + + (d.reason ? '' + translate('Reason') + ': ' + translate(d.reason) + '
' : '') + + (d.glucose ? '' + translate('BG') + ': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')' : '') + '
' : '') + + (d.enteredBy ? '' + translate('Entered By') + ': ' + d.enteredBy + '
' : '') + + (d.targetTop ? '' + translate('Target Top') + ': ' + targetTop + '
' : '') + + (d.targetBottom ? '' + translate('Target Bottom') + ': ' + targetBottom + '
' : '') + + (durationText ? '' + translate('Duration') + ': ' + durationText + '
' : '') + + (d.insulinNeedsScaleFactor ? '' + translate('Insulin Scale Factor') + ': ' + d.insulinNeedsScaleFactor * 100 + '%
' : '') + + (correctionRangeText ? '' + translate('Correction Range') + ': ' + correctionRangeText + '
' : '') + + (d.notes ? '' + translate('Notes') + ': ' + d.notes : ''); } function announcementTooltip (d) { - return ''+translate('Time')+': ' + client.formatTime(new Date(d.mills)) + '
' + - (d.eventType ? ''+translate('Announcement')+'
' : '') + - (d.notes && d.notes.length > 1 ? ''+translate('Message')+': ' + d.notes + '
' : '') + - (d.enteredBy ? ''+translate('Entered By')+': ' + d.enteredBy + '
' : ''); + return '' + translate('Time') + ': ' + client.formatTime(getOrAddDate(d)) + '
' + + (d.eventType ? '' + translate('Announcement') + '
' : '') + + (d.notes && d.notes.length > 1 ? '' + translate('Message') + ': ' + d.notes + '
' : '') + + (d.enteredBy ? '' + translate('Entered By') + ': ' + d.enteredBy + '
' : ''); } //TODO: filter in oref0 instead of here and after most people upgrade take this out @@ -195,10 +274,10 @@ function init (client, d3) { //NOTE: treatments with insulin or carbs are drawn by drawTreatment() // bind up the focus chart data to an array of circles - var treatCircles = chart().focus.selectAll('treatment-dot').data(client.ddata.treatments.filter(function(treatment) { + var treatCircles = chart().focus.selectAll('.treatment-dot').data(client.ddata.treatments.filter(function(treatment) { var notCarbsOrInsulin = !treatment.carbs && !treatment.insulin; - var notTempOrProfile = ! _.includes(['Temp Basal', 'Profile Switch', 'Combo Bolus', 'Temporary Target'], treatment.eventType); + var notTempOrProfile = !_.includes(['Temp Basal', 'Profile Switch', 'Combo Bolus', 'Temporary Target'], treatment.eventType); var notes = treatment.notes || ''; var enteredBy = treatment.enteredBy || ''; @@ -207,11 +286,26 @@ function init (client, d3) { return notes.indexOf(spam) === 0; })); - return notCarbsOrInsulin && !treatment.duration && notTempOrProfile && notOpenAPSSpam; - })); + return notCarbsOrInsulin && !treatment.duration && treatment.durationType !== 'indefinite' && notTempOrProfile && notOpenAPSSpam; + }), function (d) { return d._id; }); + + function updateTreatCircles (sel) { - function prepareTreatCircles(sel) { - function strokeColor(d) { + sel.attr('cx', function(d) { + return chart().xScale(getOrAddDate(d)); + }) + .attr('cy', function(d) { + return chart().yScale(client.sbx.scaleEntry(d)); + }) + .attr('r', function() { + return dotRadius('mbg'); + }); + + return sel; + } + + function prepareTreatCircles (sel) { + function strokeColor (d) { var color = 'white'; if (d.isAnnouncement) { color = 'orange'; @@ -221,7 +315,7 @@ function init (client, d3) { return color; } - function fillColor(d) { + function fillColor (d) { var color = 'grey'; if (d.isAnnouncement) { color = 'orange'; @@ -231,15 +325,7 @@ function init (client, d3) { return color; } - sel.attr('cx', function (d) { - return chart().xScale(new Date(d.mills)); - }) - .attr('cy', function (d) { - return chart().yScale(client.sbx.scaleEntry(d)); - }) - .attr('r', function () { - return dotRadius('mbg'); - }) + updateTreatCircles(sel) .attr('stroke-width', 2) .attr('stroke', strokeColor) .attr('fill', fillColor); @@ -248,21 +334,24 @@ function init (client, d3) { } // if already existing then transition each circle to its new position - prepareTreatCircles(treatCircles.transition()); + updateTreatCircles(treatCircles); // if new circle then just display prepareTreatCircles(treatCircles.enter().append('circle')) - .on('mouseover', function (d) { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + .attr('class', 'treatment-dot') + .on('mouseover', function(d) { + client.tooltip.style('opacity', .9); client.tooltip.html(d.isAnnouncement ? announcementTooltip(d) : treatmentTooltip(d)) .style('left', tooltipLeft()) .style('top', (d3.event.pageY + 15) + 'px'); }) .on('mouseout', hideTooltip); + treatCircles.exit().remove(); + var durationTreatments = client.ddata.treatments.filter(function(treatment) { - return !treatment.carbs && !treatment.insulin && treatment.duration && - ! _.includes(['Temp Basal', 'Profile Switch', 'Combo Bolus', 'Temporary Target'], treatment.eventType); + return !treatment.carbs && !treatment.insulin && (treatment.duration || treatment.durationType !== undefined) && + !_.includes(['Temp Basal', 'Profile Switch', 'Combo Bolus', 'Temporary Target'], treatment.eventType); }); //use the processed temp target so there are no overlaps @@ -271,7 +360,7 @@ function init (client, d3) { // treatments with duration var treatRects = chart().focus.selectAll('.g-duration').data(durationTreatments); - function fillColor(d) { + function fillColor (d) { // this is going to be updated by Event Type var color = 'grey'; if (d.eventType === 'Exercise') { @@ -297,70 +386,96 @@ function init (client, d3) { if (d.eventType === 'Temporary Target') { top = d.targetTop === d.targetBottom ? d.targetTop + rectHeight(d) : d.targetTop; } - return 'translate(' + chart().xScale(new Date(d.mills)) + ',' + chart().yScale(utils.scaleMgdl(top)) + ')'; + return 'translate(' + chart().xScale(getOrAddDate(d)) + ',' + chart().yScale(utils.scaleMgdl(top)) + ')'; } - // if already existing then transition each rect to its new position - treatRects.transition() - .attr('transform', rectTranslate); - chart().focus.selectAll('.g-duration-rect').transition() - .attr('width', function (d) { - return chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(new Date(d.mills)); - }); + function treatmentRectWidth (d) { + if (d.durationType === "indefinite") { + return chart().xScale(chart().xScale.domain()[1].getTime()) - chart().xScale(getOrAddDate(d)); + } else { + return chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(getOrAddDate(d)); + } + } - chart().focus.selectAll('.g-duration-text').transition() - .attr('transform', function (d) { - return 'translate(' + (chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(new Date(d.mills)))/2 + ',' + 10 + ')'; - }); + function treatmentTextTransform (d) { + if (d.durationType === "indefinite") { + var offset = 0; + if (chart().xScale(getOrAddDate(d)) < chart().xScale(chart().xScale.domain()[0].getTime())) { + offset = chart().xScale(nowDate) - chart().xScale(getOrAddDate(d)); + } + return 'translate(' + offset + ',' + 10 + ')'; + } else { + return 'translate(' + (chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(getOrAddDate(d))) / 2 + ',' + 10 + ')'; + } + } - // if new rect then just display - var gs = treatRects.enter().append('g') - .attr('class','g-duration') + function treatmentText (d) { + if (d.eventType === 'Temporary Target') { + return ''; + } + return d.notes || d.reason || d.eventType; + } + + function treatmentTextAnchor (d) { + return d.durationType === "indefinite" ? 'left' : 'middle'; + } + + // if transitioning, update rect text, position, and width + var rectUpdates = treatRects; + rectUpdates.attr('transform', rectTranslate); + + rectUpdates.select('text') + .text(treatmentText) + .attr('text-anchor', treatmentTextAnchor) + .attr('transform', treatmentTextTransform); + + rectUpdates.select('rect') + .attr('width', treatmentRectWidth) + + // if new rect then create new elements + var newRects = treatRects.enter().append('g') + .attr('class', 'g-duration') .attr('transform', rectTranslate) - .on('mouseover', function (d) { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + .on('mouseover', function(d) { + client.tooltip.style('opacity', .9); client.tooltip.html(d.isAnnouncement ? announcementTooltip(d) : treatmentTooltip(d)) .style('left', tooltipLeft()) .style('top', (d3.event.pageY + 15) + 'px'); }) .on('mouseout', hideTooltip); - gs.append('rect') + newRects.append('rect') .attr('class', 'g-duration-rect') - .attr('width', function (d) { - return chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(new Date(d.mills)); - }) + .attr('width', treatmentRectWidth) .attr('height', rectHeight) .attr('rx', 5) .attr('ry', 5) .attr('opacity', .2) .attr('fill', fillColor); - gs.append('text') + newRects.append('text') .attr('class', 'g-duration-text') .style('font-size', 15) .attr('fill', 'white') - .attr('text-anchor', 'middle') + .attr('text-anchor', treatmentTextAnchor) .attr('dy', '.35em') - .attr('transform', function (d) { - return 'translate(' + (chart().xScale(new Date(d.mills + times.mins(d.duration).msecs)) - chart().xScale(new Date(d.mills)))/2 + ',' + 10 + ')'; - }) - .text(function (d) { - if (d.eventType === 'Temporary Target') { - return ''; - } - return d.notes || d.eventType; - }); + .attr('transform', treatmentTextTransform) + .text(treatmentText); + + // Remove any rects no longer needed + treatRects.exit().remove(); }; - renderer.addContextCircles = function addContextCircles ( ) { + + + renderer.addContextCircles = function addContextCircles () { // bind up the context chart data to an array of circles var contextCircles = chart().context.selectAll('circle').data(client.entries); - function prepareContextCircles(sel) { + function prepareContextCircles (sel) { var badData = []; - sel.attr('cx', function (d) { return chart().xScale2(new Date(d.mills)); }) - .attr('cy', function (d) { + sel.attr('cx', function(d) { return chart().xScale2(getOrAddDate(d)); }) + .attr('cy', function(d) { var scaled = client.sbx.scaleEntry(d); if (isNaN(scaled)) { badData.push(d); @@ -369,11 +484,11 @@ function init (client, d3) { return chart().yScale2(scaled); } }) - .attr('fill', function (d) { return d.color; }) - .style('opacity', function (d) { return renderer.highlightBrushPoints(d) }) - .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) - .attr('stroke', function ( ) { return 'white'; }) - .attr('r', function (d) { return d.type === 'mbg' ? 4 : 2; }); + .attr('fill', function(d) { return d.color; }) + //.style('opacity', function(d) { return renderer.highlightBrushPoints(d) }) + .attr('stroke-width', function(d) { return d.type === 'mbg' ? 2 : 0; }) + .attr('stroke', function() { return 'white'; }) + .attr('r', function(d) { return d.type === 'mbg' ? 4 : 2; }); if (badData.length > 0) { console.warn('Bad Data: isNaN(sgv)', badData); @@ -383,7 +498,7 @@ function init (client, d3) { } // if already existing then transition each circle to its new position - prepareContextCircles(contextCircles.transition()); + prepareContextCircles(contextCircles); // if new circle then just display prepareContextCircles(contextCircles.enter().append('circle')); @@ -391,13 +506,13 @@ function init (client, d3) { contextCircles.exit().remove(); }; - function calcTreatmentRadius(treatment, opts, carbratio) { + function calcTreatmentRadius (treatment, opts, carbratio) { var CR = treatment.CR || carbratio || 20; var carbsOrInsulin = CR; - if ( treatment.carbs ) { - carbsOrInsulin = treatment.carbs; - } else if ( treatment.insulin ) { - carbsOrInsulin = treatment.insulin * CR; + if (treatment.carbs) { + carbsOrInsulin = treatment.carbs; + } else if (treatment.insulin) { + carbsOrInsulin = treatment.insulin * CR; } // R1 determines the size of the treatment dot @@ -405,8 +520,7 @@ function init (client, d3) { , R2 = R1 // R3/R4 determine how far from the treatment dot the labels are placed , R3 = R1 + 8 / opts.scale - , R4 = R1 + 25 / opts.scale - ; + , R4 = R1 + 25 / opts.scale; return { R1: R1 @@ -417,17 +531,17 @@ function init (client, d3) { }; } - function prepareArc(treatment, radius) { + function prepareArc (treatment, radius) { var arc_data = [ // white carb half-circle on top - { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': radius.R1 }, - { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': radius.R2, 'outer': radius.R3 }, + { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': radius.R1 } + , { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': radius.R2, 'outer': radius.R3 }, // blue insulin half-circle on bottom { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': radius.R1 }, // these form a very short transparent arc along the bottom of an insulin treatment to position the label // these used to be semicircles from 1.5708 to 4.7124, but that made the tooltip target too big - { 'element': '', 'color': 'transparent', 'start': 3.1400, 'end': 3.1432, 'inner': radius.R2, 'outer': radius.R3 }, - { 'element': '', 'color': 'transparent', 'start': 3.1400, 'end': 3.1432, 'inner': radius.R2, 'outer': radius.R4 } + { 'element': '', 'color': 'transparent', 'start': 3.1400, 'end': 3.1432, 'inner': radius.R2, 'outer': radius.R3 } + , { 'element': '', 'color': 'transparent', 'start': 3.1400, 'end': 3.1432, 'inner': radius.R2, 'outer': radius.R4 } ]; arc_data[0].outlineOnly = !treatment.carbs; @@ -437,20 +551,28 @@ function init (client, d3) { arc_data[1].element = Math.round(treatment.carbs) + ' g'; } + if (treatment.protein > 0) { + arc_data[1].element = arc_data[1].element + " / " + Math.round(treatment.protein) + ' g'; + } + + if (treatment.fat > 0) { + arc_data[1].element = arc_data[1].element + " / " + Math.round(treatment.fat) + ' g'; + } + if (treatment.foodType) { arc_data[1].element = arc_data[1].element + " " + treatment.foodType; } - if ( treatment.insulin > 0) { - var dosage_units = '' + Math.round(treatment.insulin * 100)/100; - + if (treatment.insulin > 0) { + var dosage_units = '' + Math.round(treatment.insulin * 100) / 100; + var unit_of_measurement = ' U'; // One international unit of insulin (1 IU) is shown as '1 U' var enteredBy = '' + treatment.enteredBy; - - if ( treatment.insulin < 1 && !treatment.carbs && enteredBy.indexOf('openaps') > -1) { // don't show the unit of measurement for insulin boluses < 1 without carbs (e.g. oref0 SMB's). Otherwise lot's of small insulin only dosages are often unreadable - unit_of_measurement = ''; - // remove leading zeros to avoid overlap with adjacent boluses - dosage_units = (dosage_units+"").replace(/^0/,""); + + if ((treatment.insulin < 1 && !treatment.carbs && enteredBy.indexOf('openaps') > -1) || treatment.isSMB) { // don't show the unit of measurement for insulin boluses < 1 without carbs (e.g. oref0 SMB's). Otherwise lot's of small insulin only dosages are often unreadable + unit_of_measurement = ''; + // remove leading zeros to avoid overlap with adjacent boluses + dosage_units = (dosage_units + "").replace(/^0/, ""); } arc_data[3].element = dosage_units + unit_of_measurement; @@ -460,17 +582,17 @@ function init (client, d3) { arc_data[4].element = translate(treatment.status); } - var arc = d3.svg.arc() - .innerRadius(function (d) { + var arc = d3.arc() + .innerRadius(function(d) { return 5 * d.inner; }) - .outerRadius(function (d) { + .outerRadius(function(d) { return 5 * d.outer; }) - .endAngle(function (d) { + .endAngle(function(d) { return d.start; }) - .startAngle(function (d) { + .startAngle(function(d) { return d.end; }); @@ -480,27 +602,27 @@ function init (client, d3) { }; } - function isInRect(x,y,rect) { + function isInRect (x, y, rect) { return !(x < rect.x || x > rect.x + rect.width || y < rect.y || y > rect.y + rect.height); } - function appendTreatments(treatment, arc) { + function appendTreatments (treatment, arc) { function boluscalcTooltip (treatment) { if (!treatment.boluscalc) { return ''; } var html = '
'; - html += (treatment.boluscalc.othercorrection ? ''+translate('Other correction')+': ' + parseFloat(treatment.boluscalc.othercorrection).toFixed(2) + 'U
' : ''); - html += (treatment.boluscalc.profile ? ''+translate('Profile used')+': ' + treatment.boluscalc.profile + '
' : ''); + html += (treatment.boluscalc.othercorrection ? '' + translate('Other correction') + ': ' + parseFloat(treatment.boluscalc.othercorrection).toFixed(2) + 'U
' : ''); + html += (treatment.boluscalc.profile ? '' + translate('Profile used') + ': ' + treatment.boluscalc.profile + '
' : ''); if (treatment.boluscalc.foods && treatment.boluscalc.foods.length) { html += ''; - for (var fi=0; fi'; - html += ''; - html += ''; + html += ''; + html += ''; + html += ''; html += ''; } html += '
' + translate('Food') + '
'+ (f.portion*f.portions).toFixed(1) + ' ' + f.unit + '('+ (f.carbs*f.portions).toFixed(1) + ' g)' + f.name + '' + (f.portion * f.portions).toFixed(1) + ' ' + f.unit + '(' + (f.carbs * f.portions).toFixed(1) + ' g)
'; @@ -508,20 +630,30 @@ function init (client, d3) { return html; } - function treatmentTooltip() { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - client.tooltip.html('' + translate('Time') + ': ' + client.formatTime(new Date(treatment.mills)) + '
' + '' + translate('Treatment type') + ': ' + translate(client.careportal.resolveEventName(treatment.eventType)) + '
' + - (treatment.carbs ? '' + translate('Carbs') + ': ' + treatment.carbs + '
' : '') + - (treatment.absorptionTime > 0 ? '' + translate('Absorption Time') + ': ' + (Math.round( treatment.absorptionTime / 60.0 * 10) / 10) + 'h' + '
' : '') + - (treatment.insulin ? '' + translate('Insulin') + ': ' + treatment.insulin + '
' : '') + - (treatment.enteredinsulin ? '' + translate('Combo Bolus') + ': ' + treatment.enteredinsulin + 'U, ' + treatment.splitNow + '% : ' + treatment.splitExt + '%, ' + translate('Duration') + ': ' + treatment.duration + '
' : '') + - (treatment.glucose ? '' + translate('BG') + ': ' + treatment.glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')' : '') + '
' : '') + - (treatment.enteredBy ? '' + translate('Entered By') + ': ' + treatment.enteredBy + '
' : '') + - (treatment.notes ? '' + translate('Notes') + ': ' + treatment.notes : '') + - boluscalcTooltip(treatment) - ) - .style('left', tooltipLeft()) - .style('top', (d3.event.pageY + 15) + 'px'); + function treatmentTooltip () { + var glucose = treatment.glucose; + if (client.settings.units != client.ddata.profile.getUnits()) { + glucose *= (client.settings.units === 'mmol' ? (1 / consts.MMOL_TO_MGDL) : consts.MMOL_TO_MGDL); + var decimals = (client.settings.units === 'mmol' ? 10 : 1); + + glucose = Math.round(glucose * decimals) / decimals; + } + + client.tooltip.style('opacity', .9); + client.tooltip.html('' + translate('Time') + ': ' + client.formatTime(getOrAddDate(treatment)) + '
' + '' + translate('Treatment type') + ': ' + translate(client.careportal.resolveEventName(treatment.eventType)) + '
' + + (treatment.carbs ? '' + translate('Carbs') + ': ' + treatment.carbs + '
' : '') + + (treatment.protein ? '' + translate('Protein') + ': ' + treatment.protein + '
' : '') + + (treatment.fat ? '' + translate('Fat') + ': ' + treatment.fat + '
' : '') + + (treatment.absorptionTime > 0 ? '' + translate('Absorption Time') + ': ' + (Math.round(treatment.absorptionTime / 60.0 * 10) / 10) + 'h' + '
' : '') + + (treatment.insulin ? '' + translate('Insulin') + ': ' + treatment.insulin + '
' : '') + + (treatment.enteredinsulin ? '' + translate('Combo Bolus') + ': ' + treatment.enteredinsulin + 'U, ' + treatment.splitNow + '% : ' + treatment.splitExt + '%, ' + translate('Duration') + ': ' + treatment.duration + '
' : '') + + (treatment.glucose ? '' + translate('BG') + ': ' + glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')' : '') + '
' : '') + + (treatment.enteredBy ? '' + translate('Entered By') + ': ' + treatment.enteredBy + '
' : '') + + (treatment.notes ? '' + translate('Notes') + ': ' + treatment.notes : '') + + boluscalcTooltip(treatment) + ) + .style('left', tooltipLeft()) + .style('top', (d3.event.pageY + 15) + 'px'); } var newTime; @@ -529,117 +661,105 @@ function init (client, d3) { var insulinRect = { x: 0, y: 0, width: 0, height: 0 }; var carbsRect = { x: 0, y: 0, width: 0, height: 0 }; var operation; - renderer.drag = d3.behavior.drag() - .on('dragstart', function() { + renderer.drag = d3.drag() + .on('start', function() { //console.log(treatment); - var windowWidth = $(client.tooltip).parent().parent().width(); + var windowWidth = $(client.tooltip.node()).parent().parent().width(); var left = d3.event.x + TOOLTIP_WIDTH < windowWidth ? d3.event.x : windowWidth - TOOLTIP_WIDTH - 10; - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9) + client.tooltip.style('opacity', .9) .style('left', left + 'px') - .style('top', (d3.event.pageY ? d3.event.pageY + 15 : 40) + 'px'); + .style('top', (d3.event.pageY ? d3.event.pageY + 15 : 40) + 'px'); deleteRect = { - x: 0, - y: 0, - width: 50, - height: chart().yScale(chart().yScale.domain()[0]) + x: 0 + , y: 0 + , width: 50 + , height: chart().yScale(chart().yScale.domain()[0]) }; chart().drag.append('rect') - .attr({ - class:'drag-droparea', - x: deleteRect.x, - y: deleteRect.y, - width: deleteRect.width, - height: deleteRect.height, - fill: 'red', - opacity: 0.4, - rx: 10, - ry: 10 - }); + .attr('class', 'drag-droparea') + .attr('x', deleteRect.x) + .attr('y', deleteRect.y) + .attr('width', deleteRect.width) + .attr('height', deleteRect.height) + .attr('fill', 'red') + .attr('opacity', 0.4) + .attr('rx', 10) + .attr('ry', 10); chart().drag.append('text') - .attr({ - class:'drag-droparea', - x: deleteRect.x + deleteRect.width / 2, - y: deleteRect.y + deleteRect.height / 2, - 'font-size': 15, - 'font-weight': 'bold', - fill: 'red', - 'text-anchor': 'middle', - dy: '.35em', - transform: 'rotate(-90 ' + (deleteRect.x + deleteRect.width / 2) + ',' + (deleteRect.y + deleteRect.height / 2) + ')' - }) + .attr('class', 'drag-droparea') + .attr('x', deleteRect.x + deleteRect.width / 2) + .attr('y', deleteRect.y + deleteRect.height / 2) + .attr('font-size', 15) + .attr('font-weight', 'bold') + .attr('fill', 'red') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') + .attr('transform', 'rotate(-90 ' + (deleteRect.x + deleteRect.width / 2) + ',' + (deleteRect.y + deleteRect.height / 2) + ')') .text(translate('Remove')); if (treatment.insulin && treatment.carbs) { carbsRect = { - x: 0, - y: 0, - width: chart().charts.attr('width'), - height: 50 + x: 0 + , y: 0 + , width: chart().charts.attr('width') + , height: 50 }; insulinRect = { - x: 0, - y: chart().yScale(chart().yScale.domain()[0]) - 50, - width: chart().charts.attr('width'), - height: 50 + x: 0 + , y: chart().yScale(chart().yScale.domain()[0]) - 50 + , width: chart().charts.attr('width') + , height: 50 }; chart().drag.append('rect') - .attr({ - class:'drag-droparea', - x: carbsRect.x, - y: carbsRect.y, - width: carbsRect.width, - height: carbsRect.height, - fill: 'white', - opacity: 0.4, - rx: 10, - ry: 10 - }); + .attr('class', 'drag-droparea') + .attr('x', carbsRect.x) + .attr('y', carbsRect.y) + .attr('width', carbsRect.width) + .attr('height', carbsRect.height) + .attr('fill', 'white') + .attr('opacitys', 0.4) + .attr('rx', 10) + .attr('ry', 10); chart().drag.append('text') - .attr({ - class:'drag-droparea', - x: carbsRect.x + carbsRect.width / 2, - y: carbsRect.y + carbsRect.height / 2, - 'font-size': 15, - 'font-weight': 'bold', - fill: 'white', - 'text-anchor': 'middle', - dy: '.35em' - }) + .attr('class', 'drag-droparea') + .attr('x', carbsRect.x + carbsRect.width / 2) + .attr('y', carbsRect.y + carbsRect.height / 2) + .attr('font-size', 15) + .attr('font-weight', 'bold') + .attr('fill', 'white') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') .text(translate('Move carbs')); chart().drag.append('rect') - .attr({ - class:'drag-droparea', - x: insulinRect.x, - y: insulinRect.y, - width: insulinRect.width, - height: insulinRect.height, - fill: '#0099ff', - opacity: 0.4, - rx: 10, - ry: 10 - }); + .attr('class', 'drag-droparea') + .attr('x', insulinRect.x) + .attr('y', insulinRect.y) + .attr('width', insulinRect.width) + .attr('height', insulinRect.height) + .attr('fill', '#0099ff') + .attr('opacity', 0.4) + .attr('rx', 10) + .attr('ry', 10); chart().drag.append('text') - .attr({ - class:'drag-droparea', - x: insulinRect.x + insulinRect.width / 2, - y: insulinRect.y + insulinRect.height / 2, - 'font-size': 15, - 'font-weight': 'bold', - fill: '#0099ff', - 'text-anchor': 'middle', - dy: '.35em' - }) + .attr('class', 'drag-droparea') + .attr('x', insulinRect.x + insulinRect.width / 2) + .attr('y', insulinRect.y + insulinRect.height / 2) + .attr('font-size', 15) + .attr('font-weight', 'bold') + .attr('fill', '#0099ff') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') .text(translate('Move insulin')); } - chart().basals.attr('display','none'); + chart().basals.attr('display', 'none'); operation = 'Move'; }) .on('drag', function() { //console.log(d3.event); - client.tooltip.transition().style('opacity', .9); + client.tooltip.style('opacity', .9); var x = Math.min(Math.max(0, d3.event.x), chart().charts.attr('width')); var y = Math.min(Math.max(0, d3.event.y), chart().focusHeight); @@ -659,42 +779,38 @@ function init (client, d3) { newTime = new Date(chart().xScale.invert(x)); var minDiff = times.msecs(newTime.getTime() - treatment.mills).mins.toFixed(0); client.tooltip.html( - '' + translate('Operation') + ': ' + translate(operation) + '
' - + '' + translate('New time') + ': ' + newTime.toLocaleTimeString() + '
' - + '' + translate('Difference') + ': ' + (minDiff > 0 ? '+' : '') + minDiff + ' ' + translate('mins') - ); + '' + translate('Operation') + ': ' + translate(operation) + '
' + + '' + translate('New time') + ': ' + newTime.toLocaleTimeString() + '
' + + '' + translate('Difference') + ': ' + (minDiff > 0 ? '+' : '') + minDiff + ' ' + translate('mins') + ); chart().drag.selectAll('.arrow').remove(); chart().drag.append('line') - .attr({ - 'class':'arrow', - 'marker-end':'url(#arrow)', - 'x1': chart().xScale(new Date(treatment.mills)), - 'y1': chart().yScale(client.sbx.scaleEntry(treatment)), - 'x2': x, - 'y2': y, - 'stroke-width': 2, - 'stroke': 'white' - }); - + .attr('class', 'arrow') + .attr('marker-end', 'url(#arrow)') + .attr('x1', chart().xScale(getOrAddDate(treatment))) + .attr('y1', chart().yScale(client.sbx.scaleEntry(treatment))) + .attr('x2', x) + .attr('y2', y) + .attr('stroke-width', 2) + .attr('stroke', 'white'); }) - .on('dragend', function() { + .on('end', function() { var newTreatment; chart().drag.selectAll('.drag-droparea').remove(); hideTooltip(); switch (operation) { case 'Move': - if (window.confirm(translate('Change treatment time to %1 ?', { params: [newTime.toLocaleTimeString()] } ))) { + if (window.confirm(translate('Change treatment time to %1 ?', { params: [newTime.toLocaleTimeString()] }))) { client.socket.emit( - 'dbUpdate', - { - collection: 'treatments', - _id: treatment._id, - data: { created_at: newTime.toISOString() } - }, - function callback(result) { + 'dbUpdate', { + collection: 'treatments' + , _id: treatment._id + , data: { created_at: newTime.toISOString() } + } + , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -704,15 +820,14 @@ function init (client, d3) { case 'Remove insulin': if (window.confirm(translate('Remove insulin from treatment ?'))) { client.socket.emit( - 'dbUpdateUnset', - { - collection: 'treatments', - _id: treatment._id, - data: { insulin: 1 } - }, - function callback(result) { + 'dbUpdateUnset', { + collection: 'treatments' + , _id: treatment._id + , data: { insulin: 1 } + } + , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -722,15 +837,14 @@ function init (client, d3) { case 'Remove carbs': if (window.confirm(translate('Remove carbs from treatment ?'))) { client.socket.emit( - 'dbUpdateUnset', - { - collection: 'treatments', - _id: treatment._id, - data: { carbs: 1 } - }, - function callback(result) { + 'dbUpdateUnset', { + collection: 'treatments' + , _id: treatment._id + , data: { carbs: 1 } + } + , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -740,14 +854,13 @@ function init (client, d3) { case 'Remove': if (window.confirm(translate('Remove treatment ?'))) { client.socket.emit( - 'dbRemove', - { - collection: 'treatments', - _id: treatment._id - }, - function callback(result) { + 'dbRemove', { + collection: 'treatments' + , _id: treatment._id + } + , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -755,13 +868,12 @@ function init (client, d3) { } break; case 'Move insulin': - if (window.confirm(translate('Change insulin time to %1 ?', { params: [newTime.toLocaleTimeString()] } ))) { + if (window.confirm(translate('Change insulin time to %1 ?', { params: [newTime.toLocaleTimeString()] }))) { client.socket.emit( - 'dbUpdateUnset', - { - collection: 'treatments', - _id: treatment._id, - data: { insulin: 1 } + 'dbUpdateUnset', { + collection: 'treatments' + , _id: treatment._id + , data: { insulin: 1 } } ); newTreatment = _.cloneDeep(treatment); @@ -770,14 +882,13 @@ function init (client, d3) { delete newTreatment.carbs; newTreatment.created_at = newTime.toISOString(); client.socket.emit( - 'dbAdd', - { - collection: 'treatments', - data: newTreatment - }, - function callback(result) { + 'dbAdd', { + collection: 'treatments' + , data: newTreatment + } + , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -785,13 +896,12 @@ function init (client, d3) { } break; case 'Move carbs': - if (window.confirm(translate('Change carbs time to %1 ?', { params: [newTime.toLocaleTimeString()] } ))) { + if (window.confirm(translate('Change carbs time to %1 ?', { params: [newTime.toLocaleTimeString()] }))) { client.socket.emit( - 'dbUpdateUnset', - { - collection: 'treatments', - _id: treatment._id, - data: { carbs: 1 } + 'dbUpdateUnset', { + collection: 'treatments' + , _id: treatment._id + , data: { carbs: 1 } } ); newTreatment = _.cloneDeep(treatment); @@ -800,14 +910,13 @@ function init (client, d3) { delete newTreatment.insulin; newTreatment.created_at = newTime.toISOString(); client.socket.emit( - 'dbAdd', - { - collection: 'treatments', - data: newTreatment - }, - function callback(result) { + 'dbAdd', { + collection: 'treatments' + , data: newTreatment + } + , function callback (result) { console.log(result); - chart().drag.selectAll('.arrow').transition().duration(5000).style('opacity', 0).remove(); + chart().drag.selectAll('.arrow').style('opacity', 0).remove(); } ); } else { @@ -815,7 +924,7 @@ function init (client, d3) { } break; } - chart().basals.attr('display',''); + chart().basals.attr('display', ''); }); var treatmentDots = chart().focus.selectAll('treatment-insulincarbs') @@ -823,7 +932,7 @@ function init (client, d3) { .enter() .append('g') .attr('class', 'draggable-treatment') - .attr('transform', 'translate(' + chart().xScale(new Date(treatment.mills)) + ', ' + chart().yScale(client.sbx.scaleEntry(treatment)) + ')') + .attr('transform', 'translate(' + chart().xScale(getOrAddDate(treatment)) + ', ' + chart().yScale(client.sbx.scaleEntry(treatment)) + ')') .on('mouseover', treatmentTooltip) .on('mouseout', hideTooltip); if (client.editMode) { @@ -834,16 +943,16 @@ function init (client, d3) { treatmentDots.append('path') .attr('class', 'path') - .attr('fill', function (d) { + .attr('fill', function(d) { return d.outlineOnly ? 'transparent' : d.color; }) - .attr('stroke-width', function (d) { + .attr('stroke-width', function(d) { return d.outlineOnly ? 1 : 0; }) - .attr('stroke', function (d) { + .attr('stroke', function(d) { return d.color; }) - .attr('id', function (d, i) { + .attr('id', function(d, i) { return 's' + i; }) .attr('d', arc.svg); @@ -851,42 +960,42 @@ function init (client, d3) { return treatmentDots; } - function appendLabels(treatmentDots, arc, opts) { + function appendLabels (treatmentDots, arc, opts) { // labels for carbs and insulin if (opts.showLabels) { var label = treatmentDots.append('g') .attr('class', 'path') .attr('id', 'label') .style('fill', 'white'); - + // reduce the treatment label font size to make it readable with SMB - var fontBaseSize = (opts.treatments >= 30) ? 40 : 50 - Math.floor((25-opts.treatments)/30 * 10); + var fontBaseSize = (opts.treatments >= 30) ? 40 : 50 - Math.floor((25 - opts.treatments) / 30 * 10); label.append('text') .style('font-size', fontBaseSize / opts.scale) .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') .attr('text-anchor', 'middle') .attr('dy', '.35em') - .attr('transform', function (d) { + .attr('transform', function(d) { d.outerRadius = d.outerRadius * 2.1; d.innerRadius = d.outerRadius * 2.1; return 'translate(' + arc.svg.centroid(d) + ')'; }) - .text(function (d) { + .text(function(d) { return d.element; }); } } - renderer.drawTreatments = function drawTreatments(client) { - + renderer.drawTreatments = function drawTreatments (client) { + var treatmentCount = 0; chart().focus.selectAll('.draggable-treatment').remove(); - + _.forEach(client.ddata.treatments, function eachTreatment (d) { - if (Number(d.insulin) > 0 || Number(d.carbs) > 0) { treatmentCount += 1; }; + if (Number(d.insulin) > 0 || Number(d.carbs) > 0) { treatmentCount += 1; } }); - + // add treatment bubbles _.forEach(client.ddata.treatments, function eachTreatment (d) { renderer.drawTreatment(d, { @@ -897,7 +1006,7 @@ function init (client, d3) { }); }; - renderer.drawTreatment = function drawTreatment(treatment, opts, carbratio) { + renderer.drawTreatment = function drawTreatment (treatment, opts, carbratio) { if (!treatment.carbs && !treatment.insulin) { return; } @@ -905,7 +1014,7 @@ function init (client, d3) { //when the tests are run window isn't available var innerWidth = window && window.innerWidth || -1; // don't render the treatment if it's not visible - if (Math.abs(chart().xScale(new Date(treatment.mills))) > innerWidth) { + if (Math.abs(chart().xScale(getOrAddDate(treatment))) > innerWidth) { return; } @@ -929,14 +1038,15 @@ function init (client, d3) { var basalareadata = []; var tempbasalareadata = []; var comboareadata = []; - var from = chart().brush.extent()[0].getTime(); - var to = Math.max(chart().brush.extent()[1].getTime(), client.sbx.time) + client.forecastTime; + var selectedRange = chart().createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); var date = from; var lastbasal = 0; if (!profile.activeProfileToTime(from)) { - window.alert(translate('Wrong profile setting.\nNo profile defined to displayed time.\nRedirecting to profile editor to create new profile.')); + window.alert(translate('Redirecting you to the Profile Editor to create a new profile.')); try { window.location.href = '/profile'; } catch (err) { @@ -948,20 +1058,20 @@ function init (client, d3) { while (date <= to) { var basalvalue = profile.getTempBasal(date); if (!_.isEqual(lastbasal, basalvalue)) { - linedata.push( { d: date, b: basalvalue.totalbasal } ); - notemplinedata.push( { d: date, b: basalvalue.basal } ); + linedata.push({ d: date, b: basalvalue.totalbasal }); + notemplinedata.push({ d: date, b: basalvalue.basal }); if (basalvalue.combobolustreatment && basalvalue.combobolustreatment.relative) { - tempbasalareadata.push( { d: date, b: basalvalue.tempbasal } ); - basalareadata.push( { d: date, b: 0 } ); - comboareadata.push( { d: date, b: basalvalue.totalbasal } ); + tempbasalareadata.push({ d: date, b: basalvalue.tempbasal }); + basalareadata.push({ d: date, b: 0 }); + comboareadata.push({ d: date, b: basalvalue.totalbasal }); } else if (basalvalue.treatment) { - tempbasalareadata.push( { d: date, b: basalvalue.totalbasal } ); - basalareadata.push( { d: date, b: 0 } ); - comboareadata.push( { d: date, b: 0 } ); + tempbasalareadata.push({ d: date, b: basalvalue.totalbasal }); + basalareadata.push({ d: date, b: 0 }); + comboareadata.push({ d: date, b: 0 }); } else { - tempbasalareadata.push( { d: date, b: 0 } ); - basalareadata.push( { d: date, b: basalvalue.totalbasal } ); - comboareadata.push( { d: date, b: 0 } ); + tempbasalareadata.push({ d: date, b: 0 }); + basalareadata.push({ d: date, b: basalvalue.totalbasal }); + comboareadata.push({ d: date, b: 0 }); } } lastbasal = basalvalue; @@ -970,15 +1080,15 @@ function init (client, d3) { var toTempBasal = profile.getTempBasal(to); - linedata.push( { d: to, b: toTempBasal.totalbasal } ); - notemplinedata.push( { d: to, b: toTempBasal.basal } ); - basalareadata.push( { d: to, b: toTempBasal.basal } ); - tempbasalareadata.push( { d: to, b: toTempBasal.totalbasal } ); - comboareadata.push( { d: to, b: toTempBasal.totalbasal } ); + linedata.push({ d: to, b: toTempBasal.totalbasal }); + notemplinedata.push({ d: to, b: toTempBasal.basal }); + basalareadata.push({ d: to, b: toTempBasal.basal }); + tempbasalareadata.push({ d: to, b: toTempBasal.totalbasal }); + comboareadata.push({ d: to, b: toTempBasal.totalbasal }); - var max_linedata = d3.max(linedata, function (d) { return d.b; }); - var max_notemplinedata = d3.max(notemplinedata, function (d) { return d.b; }); - var max = Math.max(max_linedata, max_notemplinedata) * ('icicle' === mode ? 1 : 1.1 ); + var max_linedata = d3.max(linedata, function(d) { return d.b; }); + var max_notemplinedata = d3.max(notemplinedata, function(d) { return d.b; }); + var max = Math.max(max_linedata, max_notemplinedata) * ('icicle' === mode ? 1 : 1.1); chart().maxBasalValue = max; chart().yScaleBasals.domain('icicle' === mode ? [0, max] : [max, 0]); @@ -989,16 +1099,16 @@ function init (client, d3) { chart().basals.selectAll('.tempbasalarea').remove().data(tempbasalareadata); chart().basals.selectAll('.comboarea').remove().data(comboareadata); - var valueline = d3.svg.line() - .interpolate('step-after') + var valueline = d3.line() .x(function(d) { return chart().xScaleBasals(d.d); }) - .y(function(d) { return chart().yScaleBasals(d.b); }); + .y(function(d) { return chart().yScaleBasals(d.b); }) + .curve(d3.curveStepAfter); - var area = d3.svg.area() - .interpolate('step-after') + var area = d3.area() .x(function(d) { return chart().xScaleBasals(d.d); }) .y0(chart().yScaleBasals(0)) - .y1(function(d) { return chart().yScaleBasals(d.b); }); + .y1(function(d) { return chart().yScaleBasals(d.b); }) + .curve(d3.curveStepAfter); var g = chart().basals.append('g'); @@ -1007,7 +1117,7 @@ function init (client, d3) { .attr('stroke', '#0099ff') .attr('stroke-width', 1) .attr('fill', 'none') - .attr('d', valueline(linedata)) + .attr('d', valueline(linedata)); g.append('path') .attr('class', 'line notempline') @@ -1015,7 +1125,7 @@ function init (client, d3) { .attr('stroke-width', 1) .attr('stroke-dasharray', ('3, 3')) .attr('fill', 'none') - .attr('d', valueline(notemplinedata)) + .attr('d', valueline(notemplinedata)); g.append('path') .attr('class', 'area basalarea') @@ -1050,7 +1160,7 @@ function init (client, d3) { .attr('fill', '#0099ff') .attr('text-anchor', 'middle') .attr('dy', '.35em') - .attr('x', chart().xScaleBasals((Math.max(t.mills, from) + Math.min(t.mills + times.mins(t.duration).msecs, to))/2)) + .attr('x', chart().xScaleBasals((Math.max(t.mills, from) + Math.min(t.mills + times.mins(t.duration).msecs, to)) / 2)) .attr('y', 10) .text((t.percent ? (t.percent > 0 ? '+' : '') + t.percent + '%' : '') + (isNaN(t.absolute) ? '' : Number(t.absolute).toFixed(2) + 'U') + (t.relative ? 'C: +' + t.relative + 'U' : '')); // better hide if not fit @@ -1069,19 +1179,20 @@ function init (client, d3) { } function profileTooltip (d) { - return ''+translate('Time')+': ' + client.formatTime(new Date(d.mills)) + '
' + - (d.eventType ? ''+translate('Treatment type')+': ' + translate(client.careportal.resolveEventName(d.eventType)) + '
' : '') + - (d.endprofile ? ''+translate('End of profile')+': ' + d.endprofile + '
' : '') + - (d.profile ? ''+translate('Profile')+': ' + d.profile + '
' : '') + - (d.duration ? ''+translate('Duration')+': ' + d.duration + translate('mins') + '
' : '') + - (d.enteredBy ? ''+translate('Entered By')+': ' + d.enteredBy + '
' : '') + - (d.notes ? ''+translate('Notes')+': ' + d.notes : ''); + return '' + translate('Time') + ': ' + client.formatTime(getOrAddDate(d)) + '
' + + (d.eventType ? '' + translate('Treatment type') + ': ' + translate(client.careportal.resolveEventName(d.eventType)) + '
' : '') + + (d.endprofile ? '' + translate('End of profile') + ': ' + d.endprofile + '
' : '') + + (d.profile ? '' + translate('Profile') + ': ' + d.profile + '
' : '') + + (d.duration ? '' + translate('Duration') + ': ' + d.duration + translate('mins') + '
' : '') + + (d.enteredBy ? '' + translate('Entered By') + ': ' + d.enteredBy + '
' : '') + + (d.notes ? '' + translate('Notes') + ': ' + d.notes : ''); } // calculate position of profile on left side - var from = chart().brush.extent()[0].getTime(); - var to = chart().brush.extent()[1].getTime(); - var mult = (to-from) / times.hours(24).msecs; + var selectedRange = chart().createAdjustedRange(); + var from = selectedRange[0].getTime(); + var to = selectedRange[1].getTime(); + var mult = (to - from) / times.hours(24).msecs; from += times.mins(20 * mult).msecs; var mode = client.settings.extendedSettings.basal.render; @@ -1095,12 +1206,12 @@ function init (client, d3) { _.forEach(client.ddata.profileTreatments, function eachTreatment (d) { if (d.duration && !d.cuttedby) { - data.push({ - cutting: d.profile - , profile: client.profilefunctions.activeProfileToTime(times.mins(d.duration).msecs + d.mills + 1) - , mills: times.mins(d.duration).msecs + d.mills - , end: true - }); + data.push({ + cutting: d.profile + , profile: client.profilefunctions.activeProfileToTime(times.mins(d.duration).msecs + d.mills + 1) + , mills: times.mins(d.duration).msecs + d.mills + , end: true + }); } }); @@ -1108,24 +1219,23 @@ function init (client, d3) { var topOfText = ('icicle' === mode ? chart().maxBasalValue + 0.05 : -0.05); - var generateText = function (t) { - var sign = t.first ? '▲▲▲' : '▬▬▬'; - var ret; - if (t.cutting) { - ret = sign + ' ' + t.cutting + ' ' + '►►►' + ' ' + t.profile + ' ' + sign; - } else { - ret = sign + ' ' + t.profile + ' ' + sign; - } - return ret; + var generateText = function(t) { + var sign = t.first ? '▲▲▲' : '▬▬▬'; + var ret; + if (t.cutting) { + ret = sign + ' ' + t.cutting + ' ' + '►►►' + ' ' + t.profile + ' ' + sign; + } else { + ret = sign + ' ' + t.profile + ' ' + sign; + } + return ret; }; - treatProfiles.transition().duration(0) - .attr('transform', function (t) { + treatProfiles.attr('transform', function(t) { // change text of record on left side return 'rotate(-90,' + chart().xScale(t.mills) + ',' + chart().yScaleBasals(topOfText) + ') ' + - 'translate(' + chart().xScale(t.mills) + ',' + chart().yScaleBasals(topOfText) + ')'; + 'translate(' + chart().xScale(t.mills) + ',' + chart().yScaleBasals(topOfText) + ')'; }). - text(generateText); + text(generateText); treatProfiles.enter().append('text') .attr('class', 'g-profile') @@ -1134,13 +1244,13 @@ function init (client, d3) { .attr('fill', '#0099ff') .attr('text-anchor', 'end') .attr('dy', '.35em') - .attr('transform', function (t) { + .attr('transform', function(t) { return 'rotate(-90 ' + chart().xScale(t.mills) + ',' + chart().yScaleBasals(topOfText) + ') ' + 'translate(' + chart().xScale(t.mills) + ',' + chart().yScaleBasals(topOfText) + ')'; }) .text(generateText) - .on('mouseover', function (d) { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + .on('mouseover', function(d) { + client.tooltip.style('opacity', .9); client.tooltip.html(profileTooltip(d)) .style('left', (d3.event.pageX) + 'px') .style('top', (d3.event.pageY + 15) + 'px'); diff --git a/lib/constants.json b/lib/constants.json index 31a4524b4d3..c2736f7e6e9 100644 --- a/lib/constants.json +++ b/lib/constants.json @@ -4,5 +4,6 @@ "HTTP_UNAUTHORIZED" : 401, "HTTP_VALIDATION_ERROR" : 422, "HTTP_INTERNAL_ERROR" : 500, - "ENTRIES_DEFAULT_COUNT" : 10 + "ENTRIES_DEFAULT_COUNT" : 10, + "MMOL_TO_MGDL": 18 } diff --git a/lib/data/calcdelta.js b/lib/data/calcdelta.js index 8e39885d86a..e3e0fde7052 100644 --- a/lib/data/calcdelta.js +++ b/lib/data/calcdelta.js @@ -108,7 +108,7 @@ module.exports = function calcDelta (oldData, newData) { // Calculate delta and assign delta over if changes were found var deltaData = (a === 'treatments' ? nsArrayTreatments(oldData[a], newData[a]) : nsArrayDiff(oldData[a], newData[a])); if (deltaData.length > 0) { - console.log('delta changes found on', a); + //console.log('delta changes found on', a); changesFound = true; sort(deltaData); delta[a] = deltaData; @@ -129,7 +129,7 @@ module.exports = function calcDelta (oldData, newData) { var o = skippableObjects[object]; if (newData.hasOwnProperty(o)) { if (JSON.stringify(newData[o]) !== JSON.stringify(oldData[o])) { - console.log('delta changes found on', o); + //console.log('delta changes found on', o); changesFound = true; delta[o] = newData[o]; } diff --git a/lib/data/dataloader.js b/lib/data/dataloader.js index ee6308d9846..cc4426aa6ab 100644 --- a/lib/data/dataloader.js +++ b/lib/data/dataloader.js @@ -11,6 +11,7 @@ var ONE_DAY = 86400000, function uniq(a) { var seen = {}; return a.filter(function(item) { + // eslint-disable-next-line no-prototype-builtins return seen.hasOwnProperty(item.mills) ? false : (seen[item.mills] = true); }); } @@ -222,8 +223,10 @@ function loadActivity(ddata, ctx, callback) { } function loadTreatments(ddata, ctx, callback) { + + // Load 2.5 days to cover last 48 hours including overlapping temp boluses or temp targets var dateRange = { - $gte: new Date(ddata.lastUpdated - (ONE_DAY * 8)).toISOString() + $gte: new Date(ddata.lastUpdated - (ONE_DAY * 2.5)).toISOString() }; if (ddata.page && ddata.page.frame) { dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString(); @@ -237,7 +240,6 @@ function loadTreatments(ddata, ctx, callback) { } }; - console.log('searching treatments q', tq); ctx.treatments.list(tq, function(err, results) { if (!err && results) { mergeToTreatments(ddata, results); @@ -256,14 +258,17 @@ function loadProfileSwitchTreatments(ddata, ctx, callback) { dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString(); } + // Load the latest profile switch treatment var tq = { find: { eventType: 'Profile Switch', - created_at: dateRange + created_at: dateRange, + duration: 0 }, sort: { created_at: -1 - } + }, + count: 1 }; ctx.treatments.list(tq, function(err, results) { @@ -289,6 +294,18 @@ function loadProfileSwitchTreatments(ddata, ctx, callback) { } function loadSensorAndInsulinTreatments(ddata, ctx, callback) { + async.parallel([ + loadLatestSingle.bind(null, ddata, ctx, 'Sensor Start') + ,loadLatestSingle.bind(null, ddata, ctx, 'Sensor Change') + ,loadLatestSingle.bind(null, ddata, ctx, 'Sensor Stop') + ,loadLatestSingle.bind(null, ddata, ctx, 'Site Change') + ,loadLatestSingle.bind(null, ddata, ctx, 'Insulin Change') + ,loadLatestSingle.bind(null, ddata, ctx, 'Pump Battery Change') + ], callback); +} + +function loadLatestSingle(ddata, ctx, dataType, callback) { + var dateRange = { $gte: new Date(ddata.lastUpdated - (ONE_DAY * 32)).toISOString() }; @@ -300,13 +317,14 @@ function loadSensorAndInsulinTreatments(ddata, ctx, callback) { var tq = { find: { eventType: { - $in: ['Sensor Start', 'Sensor Change', 'Insulin Change', 'Pump Battery Change'] + $eq: dataType }, created_at: dateRange }, sort: { created_at: -1 - } + }, + count: 1 }; ctx.treatments.list(tq, function(err, results) { diff --git a/lib/data/ddata.js b/lib/data/ddata.js index a055b9f3049..9fa470e3f16 100644 --- a/lib/data/ddata.js +++ b/lib/data/ddata.js @@ -2,273 +2,242 @@ var _ = require('lodash'); var times = require('../times'); +var consts = require('../constants'); var DEVICE_TYPE_FIELDS = ['uploader', 'pump', 'openaps', 'loop', 'xdripjs']; -function init() { - - var ddata = { - sgvs: [], - treatments: [], - mbgs: [], - cals: [], - profiles: [], - devicestatus: [], - food: [], - activity: [], - lastUpdated: 0 - }; - - ddata.clone = function clone() { - return _.clone(ddata, function(value) { - //special handling of mongo ObjectID's - //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 - - //instead of requiring Mongo.ObjectID here and having it get pulled into the bundle - //we'll look for the toHexString function and then assume it's an ObjectID - if (value && value.toHexString && value.toHexString.call && value.toString && value.toString.call) { - return value.toString(); - } - }); - }; - - ddata.splitRecent = function splitRecent(time, cutoff, max, treatmentsToo) { - var result = { - first: {}, - rest: {} - }; - - function recent(item) { - return item.mills >= time - cutoff; - } - - function filterMax(item) { - return item.mills >= time - max; +function init () { + + var ddata = { + sgvs: [] + , treatments: [] + , mbgs: [] + , cals: [] + , profiles: [] + , devicestatus: [] + , food: [] + , activity: [] + , lastUpdated: 0 + }; + + ddata.clone = function clone () { + return _.clone(ddata, function(value) { + //special handling of mongo ObjectID's + //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 + + //instead of requiring Mongo.ObjectID here and having it get pulled into the bundle + //we'll look for the toHexString function and then assume it's an ObjectID + if (value && value.toHexString && value.toHexString.call && value.toString && value.toString.call) { + return value.toString(); + } + }); + }; + + ddata.dataWithRecentStatuses = function dataWithRecentStatuses() { + var results = {}; + results.devicestatus = ddata.recentDeviceStatus(Date.now()); + results.sgvs = ddata.sgvs; + results.cals = ddata.cals; + + var profiles = _.cloneDeep(ddata.profiles); + if (profiles && profiles[0]) { + Object.keys(profiles[0].store).forEach(k => { + if (k.indexOf('@@@@@') > 0) { + delete profiles[0].store[k]; } - - function partition(field, filter) { - var data; - if (filter) { - data = ddata[field].filter(filterMax); - } else { - data = ddata[field]; - } - - var parts = _.partition(data, recent); - result.first[field] = parts[0]; - result.rest[field] = parts[1]; + }) + } + results.profiles = profiles; + results.mbgs = ddata.mbgs; + results.food = ddata.food; + results.treatments = ddata.treatments; + + return results; + + } + + ddata.recentDeviceStatus = function recentDeviceStatus (time) { + + var deviceAndTypes = + _.chain(ddata.devicestatus) + .map(function eachStatus (status) { + return _.chain(status) + .keys() + .filter(function isExcluded (key) { + return _.includes(DEVICE_TYPE_FIELDS, key); + }) + .map(function toDeviceTypeKey (key) { + return { + device: status.device + , type: key + }; + }) + .value(); + }) + .flatten() + .uniqWith(_.isEqual) + .value(); + + //console.info('>>>deviceAndTypes', deviceAndTypes); + + var rv = _.chain(deviceAndTypes) + .map(function findMostRecent (deviceAndType) { + return _.chain(ddata.devicestatus) + .filter(function isSameDeviceType (status) { + return status.device === deviceAndType.device && _.has(status, deviceAndType.type) + }) + .filter(function notInTheFuture (status) { + return status.mills <= time; + }) + .sortBy('mills') + .takeRight(10) + .value(); + }).value(); + + var merged = [].concat.apply([], rv); + + rv = _.chain(merged) + .filter(_.isObject) + .uniq('_id') + .sortBy('mills') + .value(); + + return rv; + + }; + + ddata.processDurations = function processDurations (treatments, keepzeroduration) { + + treatments = _.uniqBy(treatments, 'mills'); + + // cut temp basals by end events + // better to do it only on data update + var endevents = treatments.filter(function filterEnd (t) { + return !t.duration; + }); + + function cutIfInInterval (base, end) { + if (base.mills < end.mills && base.mills + times.mins(base.duration).msecs > end.mills) { + base.duration = times.msecs(end.mills - base.mills).mins; + if (end.profile) { + base.cuttedby = end.profile; + end.cutting = base.profile; } - - partition('treatments', treatmentsToo ? filterMax : false); - - result.first.devicestatus = ddata.recentDeviceStatus(time); - - result.first.sgvs = ddata.sgvs.filter(filterMax); - result.first.cals = ddata.cals; - - var profiles = _.cloneDeep(ddata.profiles); - if (profiles && profiles[0]) - for (var k in profiles[0].store) { - if (profiles[0].store.hasOwnProperty(k)) { - if (k.indexOf('@@@@@') > 0) { - delete profiles[0].store[k]; - } - } - } - result.first.profiles = profiles; - - result.rest.mbgs = ddata.mbgs.filter(filterMax); - result.rest.food = ddata.food; - result.rest.activity = ddata.activity; - - console.log('results.first size', JSON.stringify(result.first).length, 'bytes'); - console.log('results.rest size', JSON.stringify(result.rest).length, 'bytes'); - - return result; - }; - - ddata.recentDeviceStatus = function recentDeviceStatus(time) { - - var deviceAndTypes = - _.chain(ddata.devicestatus) - .map(function eachStatus(status) { - return _.chain(status) - .keys() - .filter(function isExcluded(key) { - return _.includes(DEVICE_TYPE_FIELDS, key); - }) - .map(function toDeviceTypeKey(key) { - return { - device: status.device, - type: key - }; - }) - .value(); - }) - .flatten() - .uniqWith(_.isEqual) - .value(); - - //console.info('>>>deviceAndTypes', deviceAndTypes); - - var rv = _.chain(deviceAndTypes) - .map(function findMostRecent(deviceAndType) { - return _.chain(ddata.devicestatus) - .filter(function isSameDeviceType(status) { - return status.device === deviceAndType.device && _.has(status, deviceAndType.type) - }) - .filter(function notInTheFuture(status) { - return status.mills <= time; - }) - .sortBy('mills') - .takeRight(10) - .value(); - }).value(); - - var merged = [].concat.apply([], rv); - - rv = _.chain(merged) - .filter(_.isObject) - .uniq('_id') - .sortBy('mills') - .value(); - - return rv; - - }; - - ddata.processDurations = function processDurations(treatments, keepzeroduration) { - - treatments = _.uniqBy(treatments, 'mills'); - - // cut temp basals by end events - // better to do it only on data update - var endevents = treatments.filter(function filterEnd(t) { - return !t.duration; + } + } + + // cut by end events + treatments.forEach(function allTreatments (t) { + if (t.duration) { + endevents.forEach(function allEndevents (e) { + cutIfInInterval(t, e); }); - - function cutIfInInterval(base, end) { - if (base.mills < end.mills && base.mills + times.mins(base.duration).msecs > end.mills) { - base.duration = times.msecs(end.mills - base.mills).mins; - if (end.profile) { - base.cuttedby = end.profile; - end.cutting = base.profile; - } - } - } - - // cut by end events - treatments.forEach(function allTreatments(t) { - if (t.duration) { - endevents.forEach(function allEndevents(e) { - cutIfInInterval(t, e); - }); - } - }); - - // cut by overlaping events - treatments.forEach(function allTreatments(t) { - if (t.duration) { - treatments.forEach(function allEndevents(e) { - cutIfInInterval(t, e); - }); - } + } + }); + + // cut by overlaping events + treatments.forEach(function allTreatments (t) { + if (t.duration) { + treatments.forEach(function allEndevents (e) { + cutIfInInterval(t, e); }); - - if (keepzeroduration) { - return treatments; - } else { - return treatments.filter(function filterEnd(t) { - return t.duration; - }); + } + }); + + if (keepzeroduration) { + return treatments; + } else { + return treatments.filter(function filterEnd (t) { + return t.duration; + }); + } + }; + + ddata.processTreatments = function processTreatments (preserveOrignalTreatments) { + + // filter & prepare 'Site Change' events + ddata.sitechangeTreatments = ddata.treatments.filter(function filterSensor (t) { + return t.eventType.indexOf('Site Change') > -1; + }).sort(function(a, b) { + return a.mills > b.mills; + }); + + // filter & prepare 'Insulin Change' events + ddata.insulinchangeTreatments = ddata.treatments.filter(function filterInsulin (t) { + return t.eventType.indexOf('Insulin Change') > -1; + }).sort(function(a, b) { + return a.mills > b.mills; + }); + + // filter & prepare 'Pump Battery Change' events + ddata.batteryTreatments = ddata.treatments.filter(function filterSensor (t) { + return t.eventType.indexOf('Pump Battery Change') > -1; + }).sort(function(a, b) { + return a.mills > b.mills; + }); + + // filter & prepare 'Sensor' events + ddata.sensorTreatments = ddata.treatments.filter(function filterSensor (t) { + return t.eventType.indexOf('Sensor') > -1; + }).sort(function(a, b) { + return a.mills > b.mills; + }); + + // filter & prepare 'Profile Switch' events + var profileTreatments = ddata.treatments.filter(function filterProfiles (t) { + return t.eventType === 'Profile Switch'; + }).sort(function(a, b) { + return a.mills > b.mills; + }); + if (preserveOrignalTreatments) + profileTreatments = _.cloneDeep(profileTreatments); + ddata.profileTreatments = ddata.processDurations(profileTreatments, true); + + // filter & prepare 'Combo Bolus' events + ddata.combobolusTreatments = ddata.treatments.filter(function filterComboBoluses (t) { + return t.eventType === 'Combo Bolus'; + }).sort(function(a, b) { + return a.mills > b.mills; + }); + + // filter & prepare temp basals + var tempbasalTreatments = ddata.treatments.filter(function filterBasals (t) { + return t.eventType && t.eventType.indexOf('Temp Basal') > -1; + }); + if (preserveOrignalTreatments) + tempbasalTreatments = _.cloneDeep(tempbasalTreatments); + ddata.tempbasalTreatments = ddata.processDurations(tempbasalTreatments, false); + + // filter temp target + var tempTargetTreatments = ddata.treatments.filter(function filterTargets (t) { + //check for a units being sent + if (t.units) { + if (t.units == 'mmol') { + //convert to mgdl + t.targetTop = t.targetTop * consts.MMOL_TO_MGDL; + t.targetBottom = t.targetBottom * consts.MMOL_TO_MGDL; + t.units = 'mg/dl'; } - }; - - ddata.processTreatments = function processTreatments(preserveOrignalTreatments) { - - // filter & prepare 'Site Change' events - ddata.sitechangeTreatments = ddata.treatments.filter(function filterSensor(t) { - return t.eventType.indexOf('Site Change') > -1; - }).sort(function(a, b) { - return a.mills > b.mills; - }); - - // filter & prepare 'Insulin Change' events - ddata.insulinchangeTreatments = ddata.treatments.filter(function filterInsulin(t) { - return t.eventType.indexOf('Insulin Change') > -1; - }).sort(function(a, b) { - return a.mills > b.mills; - }); - - // filter & prepare 'Pump Battery Change' events - ddata.batteryTreatments = ddata.treatments.filter(function filterSensor(t) { - return t.eventType.indexOf('Pump Battery Change') > -1; - }).sort(function(a, b) { - return a.mills > b.mills; - }); - - // filter & prepare 'Sensor' events - ddata.sensorTreatments = ddata.treatments.filter(function filterSensor(t) { - return t.eventType.indexOf('Sensor') > -1; - }).sort(function(a, b) { - return a.mills > b.mills; - }); - - // filter & prepare 'Profile Switch' events - var profileTreatments = ddata.treatments.filter(function filterProfiles(t) { - return t.eventType === 'Profile Switch'; - }).sort(function(a, b) { - return a.mills > b.mills; - }); - if (preserveOrignalTreatments) - profileTreatments = _.cloneDeep(profileTreatments); - ddata.profileTreatments = ddata.processDurations(profileTreatments, true); - - // filter & prepare 'Combo Bolus' events - ddata.combobolusTreatments = ddata.treatments.filter(function filterComboBoluses(t) { - return t.eventType === 'Combo Bolus'; - }).sort(function(a, b) { - return a.mills > b.mills; - }); - - // filter & prepare temp basals - var tempbasalTreatments = ddata.treatments.filter(function filterBasals(t) { - return t.eventType && t.eventType.indexOf('Temp Basal') > -1; - }); - if (preserveOrignalTreatments) - tempbasalTreatments = _.cloneDeep(tempbasalTreatments); - ddata.tempbasalTreatments = ddata.processDurations(tempbasalTreatments, false); - - // filter temp target - var tempTargetTreatments = ddata.treatments.filter(function filterTargets(t) { - //check for a units being sent - if (t.units) { - if (t.units == 'mmol') { - //convert to mgdl - t.targetTop = t.targetTop * 18; - t.targetBottom = t.targetBottom * 18; - t.units = 'mg/dl'; - } - } - //if we have a temp target thats below 20, assume its mmol and convert to mgdl for safety. - if (t.targetTop < 20) { - t.targetTop = t.targetTop * 18; - t.units = 'mg/dl'; - } - if (t.targetBottom < 20) { - t.targetBottom = t.targetBottom * 18; - t.units = 'mg/dl'; - } - return t.eventType && t.eventType.indexOf('Temporary Target') > -1; - }); - if (preserveOrignalTreatments) - tempTargetTreatments = _.cloneDeep(tempTargetTreatments); - ddata.tempTargetTreatments = ddata.processDurations(tempTargetTreatments, false); - - }; - - return ddata; + } + //if we have a temp target thats below 20, assume its mmol and convert to mgdl for safety. + if (t.targetTop < 20) { + t.targetTop = t.targetTop * consts.MMOL_TO_MGDL; + t.units = 'mg/dl'; + } + if (t.targetBottom < 20) { + t.targetBottom = t.targetBottom * consts.MMOL_TO_MGDL; + t.units = 'mg/dl'; + } + return t.eventType && t.eventType.indexOf('Temporary Target') > -1; + }); + if (preserveOrignalTreatments) + tempTargetTreatments = _.cloneDeep(tempTargetTreatments); + ddata.tempTargetTreatments = ddata.processDurations(tempTargetTreatments, false); + + }; + + return ddata; } -module.exports = init; \ No newline at end of file +module.exports = init; diff --git a/lib/data/treatmenttocurve.js b/lib/data/treatmenttocurve.js index a96d3a795ce..afa17b397ec 100644 --- a/lib/data/treatmenttocurve.js +++ b/lib/data/treatmenttocurve.js @@ -1,6 +1,10 @@ 'use strict'; var _ = require('lodash'); +var consts = require('../constants'); + +const MAX_BG_MMOL = 22; +const MAX_BG_MGDL = MAX_BG_MMOL * consts.MMOL_TO_MGDL; module.exports = function fitTreatmentsToBGCurve (ddata, env, ctx) { @@ -62,15 +66,16 @@ module.exports = function fitTreatmentsToBGCurve (ddata, env, ctx) { console.warn('found an invalid glucose value', treatment); } else if (treatment.glucose && treatment.units) { if (treatment.units === 'mmol') { - treatment.mmol = Number(treatment.glucose); + treatment.mmol = Math.min(Number(treatment.glucose), MAX_BG_MMOL); } else { - treatment.mgdl = Number(treatment.glucose); + treatment.mgdl = Math.min(Number(treatment.glucose), MAX_BG_MGDL); } } else if (treatment.glucose) { //no units, assume everything is the same //console.warn('found a glucose value without any units, maybe from an old version?', _.pick(treatment, '_id', 'created_at', 'enteredBy')); var units = settings.units === 'mmol' ? 'mmol' : 'mgdl'; - treatment[units] = Number(treatment.glucose); + + treatment[units] = settings.units === 'mmol' ? Math.min(Number(treatment.glucose), MAX_BG_MMOL) : Math.min(Number(treatment.glucose), MAX_BG_MGDL); } else { treatment.mgdl = mgdlByTime(); } diff --git a/lib/hashauth.js b/lib/hashauth.js index e4956860c59..8848ca08ef0 100644 --- a/lib/hashauth.js +++ b/lib/hashauth.js @@ -9,6 +9,7 @@ var hashauth = { , apisecrethash: null , authenticated: false , initialized: false + , tokenauthenticated: false }; hashauth.init = function init(client, $) { @@ -24,15 +25,27 @@ hashauth.init = function init(client, $) { , url: '/api/v1/verifyauth?t=' + Date.now() //cache buster , headers: client.headers() }).done(function verifysuccess (response) { - if (response.message === 'OK') { + + if (response.message.rolefound == 'FOUND') { + hashauth.tokenauthenticated = true; + console.log('Token Authentication passed.'); + client.authorizeSocket(); + next(true); + return; + } + + if (response.message.message === 'OK') { hashauth.authenticated = true; console.log('Authentication passed.'); next(true); - } else { - console.log('Authentication failed.', response); + return; + } + + console.log('Authentication failed.', response); hashauth.removeAuthentication(); next(false); - } + return; + }).fail(function verifyfail (err) { console.log('Authentication failed.', err); hashauth.removeAuthentication(); @@ -55,12 +68,12 @@ hashauth.init = function init(client, $) { }); return hashauth; }; - + hashauth.removeAuthentication = function removeAuthentication(event) { Storages.localStorage.remove('apisecrethash'); - if (hashauth.authenticated) { + if (hashauth.authenticated || hashauth.tokenauthenticated) { client.browserUtils.reload(); } @@ -74,16 +87,25 @@ hashauth.init = function init(client, $) { } return false; }; - + hashauth.requestAuthentication = function requestAuthentication (eventOrNext) { var translate = client.translate; hashauth.injectHtml(); + + var clientWidth = window.innerWidth + || document.documentElement.clientWidth + || document.body.clientWidth; + + clientWidth = Math.min(400, clientWidth); + $( '#requestauthenticationdialog' ).dialog({ - width: 500 - , height: 240 + width: clientWidth + , height: 270 + , closeText: '' , buttons: [ { - text: translate('Update') + id: 'requestauthenticationdialog-btn' + , text: translate('Update') , click: function() { var dialog = this; hashauth.processSecret($('#apisecret').val(), $('#storeapisecret').is(':checked'), function done (close) { @@ -102,9 +124,9 @@ hashauth.init = function init(client, $) { } ] , open: function open ( ) { - $('#requestauthenticationdialog').keypress(function pressed (e) { + $('#apisecret').off('keyup').on('keyup' ,function pressed (e) { if (e.keyCode === $.ui.keyCode.ENTER) { - $(this).parent().find('button.ui-button-text-only').trigger('click'); + $('#requestauthenticationdialog-btn').trigger('click'); } }); $('#apisecret').val('').focus(); @@ -117,7 +139,7 @@ hashauth.init = function init(client, $) { } return false; }; - + hashauth.processSecret = function processSecret(apisecret, storeapisecret, callback) { var translate = client.translate; @@ -137,6 +159,8 @@ hashauth.init = function init(client, $) { if (isok) { if (hashauth.storeapisecret) { Storages.localStorage.set('apisecrethash',hashauth.apisecrethash); + // TODO show dialog first, then reload + if (hashauth.tokenauthenticated) client.browserUtils.reload(); } $('#authentication_placeholder').html(hashauth.inlineCode()); if (callback) { @@ -157,9 +181,18 @@ hashauth.init = function init(client, $) { var status = null; - if (client.authorized) { - status = translate('Authorized by token') + ' (' + translate('view without token') + ')' + - '
' + client.authorized.sub + ': ' + client.authorized.permissionGroups.join(', ') + ''; + if (client.authorized || hashauth.tokenauthenticated) { + status = translate('Authorized by token'); + if (client.authorized && client.authorized.sub) { + status += '
' + client.authorized.sub + ': ' + client.authorized.permissionGroups.join(', ') + ''; + } + if (hashauth.apisecrethash) + { + status += '
(' + translate('Remove stored token') + ')'; + } else { + status += '
(' + translate('view without token') + ')'; + } + } else if (hashauth.isAuthenticated()) { console.info('status isAuthenticated', hashauth); status = translate('Admin authorized') + ' (' + translate('Remove') + ')'; @@ -169,13 +202,10 @@ hashauth.init = function init(client, $) { var html = ''+ '
' + status + '
'; @@ -204,7 +234,7 @@ hashauth.init = function init(client, $) { }; hashauth.isAuthenticated = function isAuthenticated() { - return hashauth.authenticated; + return hashauth.authenticated || hashauth.tokenauthenticated; }; hashauth.initialized = true; diff --git a/lib/language.js b/lib/language.js index e5e879c0929..87fb871e1d1 100644 --- a/lib/language.js +++ b/lib/language.js @@ -24,6 +24,7 @@ function init() { , { code: 'he', language: 'עברית', speechCode: 'he-IL' } , { code: 'hr', language: 'Hrvatski', speechCode: 'hr-HR' } , { code: 'it', language: 'Italiano', speechCode: 'it-IT' } + , { code: 'ja', language: '日本語', speechCode: 'ja-JP' } , { code: 'ko', language: '한국어', speechCode: 'ko-KR' } , { code: 'nb', language: 'Norsk (Bokmål)', speechCode: 'no-NO' } , { code: 'nl', language: 'Nederlands', speechCode: 'nl-NL' } @@ -33,6 +34,7 @@ function init() { , { code: 'ru', language: 'Русский', speechCode: 'ru-RU' } , { code: 'sk', language: 'Slovenčina', speechCode: 'sk-SK' } , { code: 'sv', language: 'Svenska', speechCode: 'sv-SE' } + , { code: 'tr', language: 'Türkçe', speechCode: 'tr-TR' } , { code: 'zh_cn', language: '中文(简体)', speechCode: 'cmn-Hans-CN' } , { code: 'zh_tw', language: '中文(繁體)', speechCode: 'cmn-Hant-TW' } ]; @@ -51,6 +53,7 @@ function init() { ,bg: 'Активиране на порта' ,hr: 'Slušanje na portu' ,it: 'Porta in ascolto' + ,ja: '接続可能' ,fi: 'Kuuntelen porttia' ,nb: 'Lytter på port' ,he: 'מקשיב על פתחה' @@ -60,6 +63,7 @@ function init() { ,de: 'Lauscht auf Port' ,nl: 'Luistert op poort' ,ko: '포트에서 수신' + ,tr: 'Port dinleniyor' ,zh_cn: '正在监听端口' } // Client @@ -75,6 +79,7 @@ function init() { ,bg: 'Пон' ,hr: 'Pon' ,it: 'Lun' + ,ja: '月' ,dk: 'Man' ,fi: 'Ma' ,nb: 'Man' @@ -84,6 +89,7 @@ function init() { ,sk: 'Po' ,nl: 'Ma' ,ko: '월' + ,tr: 'Pzt' ,zh_cn: '一' } ,'Tu' : { @@ -96,8 +102,9 @@ function init() { ,sv: 'Tis' ,ro: 'Ma' ,bg: 'Вт' - ,hr: 'Ut' + ,hr: 'Uto' ,it: 'Mar' + ,ja: '火' ,dk: 'Tir' ,fi: 'Ti' ,nb: 'Tir' @@ -107,6 +114,7 @@ function init() { ,sk: 'Ut' ,nl: 'Di' ,ko: '화' + ,tr: 'Sal' ,zh_cn: '二' } ,'We' : { @@ -121,6 +129,7 @@ function init() { ,bg: 'Ср' ,hr: 'Sri' ,it: 'Mer' + ,ja: '水' ,dk: 'Ons' ,fi: 'Ke' ,nb: 'Ons' @@ -130,6 +139,7 @@ function init() { ,sk: 'St' ,nl: 'Wo' ,ko: '수' + ,tr: 'Çar' ,zh_cn: '三' } ,'Th' : { @@ -144,6 +154,7 @@ function init() { ,bg: 'Четв' ,hr: 'Čet' ,it: 'Gio' + ,ja: '木' ,dk: 'Tor' ,fi: 'To' ,nb: 'Tor' @@ -153,6 +164,7 @@ function init() { ,sk: 'Št' ,nl: 'Do' ,ko: '목' + ,tr: 'Per' ,zh_cn: '四' } ,'Fr' : { @@ -167,6 +179,7 @@ function init() { ,bg: 'Пет' ,hr: 'Pet' ,it: 'Ven' + ,ja: '金' ,dk: 'Fre' ,fi: 'Pe' ,nb: 'Fre' @@ -176,6 +189,7 @@ function init() { ,sk: 'Pi' ,nl: 'Vr' ,ko: '금' + ,tr: 'Cum' ,zh_cn: '五' } ,'Sa' : { @@ -190,6 +204,7 @@ function init() { ,bg: 'Съб' ,hr: 'Sub' ,it: 'Sab' + ,ja: '土' ,dk: 'Lør' ,fi: 'La' ,nb: 'Lør' @@ -199,6 +214,7 @@ function init() { ,sk: 'So' ,nl: 'Za' ,ko: '토' + ,tr: 'Cmt' ,zh_cn: '六' } ,'Su' : { @@ -213,6 +229,7 @@ function init() { ,bg: 'Нед' ,hr: 'Ned' ,it: 'Dom' + ,ja: '日' ,dk: 'Søn' ,fi: 'Su' ,nb: 'Søn' @@ -222,6 +239,7 @@ function init() { ,sk: 'Ne' ,nl: 'Zo' ,ko: '일' + ,tr: 'Paz' ,zh_cn: '日' } ,'Monday' : { @@ -236,6 +254,7 @@ function init() { ,bg: 'Понеделник' ,hr: 'Ponedjeljak' ,it: 'Lunedì' + ,ja: '月曜日' ,dk: 'Mandag' ,fi: 'Maanantai' ,nb: 'Mandag' @@ -245,6 +264,7 @@ function init() { ,sk: 'Pondelok' ,nl: 'Maandag' ,ko: '월요일' + ,tr: 'Pazartesi' ,zh_cn: '星期一' } ,'Tuesday' : { @@ -259,6 +279,7 @@ function init() { ,hr: 'Utorak' ,sv: 'Tisdag' ,it: 'Martedì' + ,ja: '火曜日' ,dk: 'Tirsdag' ,fi: 'Tiistai' ,nb: 'Tirsdag' @@ -268,6 +289,7 @@ function init() { ,sk: 'Utorok' ,nl: 'Dinsdag' ,ko: '화요일' + ,tr: 'Salı' ,zh_cn: '星期二' } ,'Wednesday' : { @@ -282,6 +304,7 @@ function init() { ,bg: 'Сряда' ,hr: 'Srijeda' ,it: 'Mercoledì' + ,ja: '水曜日' ,dk: 'Onsdag' ,fi: 'Keskiviikko' ,nb: 'Onsdag' @@ -291,6 +314,7 @@ function init() { ,sk: 'Streda' ,nl: 'Woensdag' ,ko: '수요일' + ,tr: 'Çarşamba' ,zh_cn: '星期三' } ,'Thursday' : { @@ -305,6 +329,7 @@ function init() { ,bg: 'Четвъртък' ,hr: 'Četvrtak' ,it: 'Giovedì' + ,ja: '木曜日' ,dk: 'Torsdag' ,fi: 'Torstai' ,nb: 'Torsdag' @@ -314,6 +339,7 @@ function init() { ,sk: 'Štvrtok' ,nl: 'Donderdag' ,ko: '목요일' + ,tr: 'Perşembe' ,zh_cn: '星期四' } ,'Friday' : { @@ -328,6 +354,7 @@ function init() { ,bg: 'Петък' ,hr: 'Petak' ,it: 'Venerdì' + ,ja: '金曜日' ,dk: 'Fredag' ,fi: 'Perjantai' ,nb: 'Fredag' @@ -337,6 +364,7 @@ function init() { ,sk: 'Piatok' ,nl: 'Vrijdag' ,ko: '금요일' + ,tr: 'Cuma' ,zh_cn: '星期五' } ,'Saturday' : { @@ -351,6 +379,7 @@ function init() { ,hr: 'Subota' ,sv: 'Lördag' ,it: 'Sabato' + ,ja: '土曜日' ,dk: 'Lørdag' ,fi: 'Lauantai' ,nb: 'Lørdag' @@ -360,6 +389,7 @@ function init() { ,sk: 'Sobota' ,nl: 'Zaterdag' ,ko: '토요일' + ,tr: 'Cumartesi' ,zh_cn: '星期六' } ,'Sunday' : { @@ -374,6 +404,7 @@ function init() { ,hr: 'Nedjelja' ,sv: 'Söndag' ,it: 'Domenica' + ,ja: '日曜日' ,dk: 'Søndag' ,fi: 'Sunnuntai' ,nb: 'Søndag' @@ -383,6 +414,7 @@ function init() { ,sk: 'Nedeľa' ,nl: 'Zondag' ,ko: '일요일' + ,tr: 'Pazar' ,zh_cn: '星期日' } ,'Category' : { @@ -397,6 +429,7 @@ function init() { ,bg: 'Категория' ,hr: 'Kategorija' ,it:'Categoria' + ,ja: 'カテゴリー' ,dk: 'Kategori' ,fi: 'Luokka' ,nb: 'Kategori' @@ -406,6 +439,7 @@ function init() { ,sk: 'Kategória' ,nl: 'Categorie' ,ko: '분류' + ,tr: 'Kategori' ,zh_cn: '类别' } ,'Subcategory' : { @@ -420,6 +454,7 @@ function init() { ,bg: 'Подкатегория' ,hr: 'Podkategorija' ,it: 'Sottocategoria' + ,ja: 'サブカテゴリー' ,dk: 'Underkategori' ,fi: 'Alaluokka' ,nb: 'Underkategori' @@ -429,6 +464,7 @@ function init() { ,sk: 'Podkategória' ,nl: 'Subcategorie' ,ko: '세부 분류' + ,tr: 'Altkategori' ,zh_cn: '子类别' } ,'Name' : { @@ -443,6 +479,7 @@ function init() { ,bg: 'Име' ,hr: 'Ime' ,it: 'Nome' + ,ja: '名前' ,dk: 'Navn' ,fi: 'Nimi' ,nb: 'Navn' @@ -451,7 +488,8 @@ function init() { ,ru: 'Имя' ,sk: 'Meno' ,nl: 'Naam' - ,ko: '이름' + ,ko: '프로파일 명' + ,tr: 'İsim' ,zh_cn: '名称' } ,'Today' : { @@ -466,6 +504,7 @@ function init() { ,hr: 'Danas' ,sv: 'Idag' ,it: 'Oggi' + ,ja: '今日' ,dk: 'I dag' ,fi: 'Tänään' ,nb: 'Idag' @@ -475,6 +514,7 @@ function init() { ,sk: 'Dnes' ,nl: 'Vandaag' ,ko: '오늘' + ,tr: 'Bugün' ,zh_cn: '今天' } ,'Last 2 days' : { @@ -489,6 +529,7 @@ function init() { ,hr: 'Posljednja 2 dana' ,sv: 'Senaste 2 dagarna' ,it: 'Ultimi 2 giorni' + ,ja: '直近の2日間' ,dk: 'Sidste 2 dage' ,fi: 'Edelliset 2 päivää' ,nb: 'Siste 2 dager' @@ -498,6 +539,7 @@ function init() { ,sk: 'Posledné 2 dni' ,nl: 'Afgelopen 2 dagen' ,ko: '지난 2일' + ,tr: 'Son 2 gün' ,zh_cn: '过去2天' } ,'Last 3 days' : { @@ -512,6 +554,7 @@ function init() { ,bg: 'Последните 3 дни' ,hr: 'Posljednja 3 dana' ,it: 'Ultimi 3 giorni' + ,ja: '直近の3日間' ,dk: 'Sidste 3 dage' ,fi: 'Edelliset 3 päivää' ,nb: 'Siste 3 dager' @@ -521,6 +564,7 @@ function init() { ,sk: 'Posledné 3 dni' ,nl: 'Afgelopen 3 dagen' ,ko: '지난 3일' + ,tr: 'Son 3 gün' ,zh_cn: '过去3天' } ,'Last week' : { @@ -535,6 +579,7 @@ function init() { ,hr: 'Protekli tjedan' ,sv: 'Senaste veckan' ,it: 'Settimana scorsa' + ,ja: '直近の1週間' ,dk: 'Sidste uge' ,fi: 'Viime viikko' ,nb: 'Siste uke' @@ -544,6 +589,7 @@ function init() { ,sk: 'Posledný týždeň' ,nl: 'Afgelopen week' ,ko: '지난주' + ,tr: 'Geçen Hafta' ,zh_cn: '上周' } ,'Last 2 weeks' : { @@ -558,6 +604,7 @@ function init() { ,hr: 'Protekla 2 tjedna' ,sv: 'Senaste 2 veckorna' ,it: 'Ultime 2 settimane' + ,ja: '直近の2週間' ,dk: 'Sidste 2 uger' ,fi: 'Viimeiset 2 viikkoa' ,nb: 'Siste 2 uker' @@ -567,6 +614,7 @@ function init() { ,sk: 'Posledné 2 týždne' ,nl: 'Afgelopen 2 weken' ,ko: '지난 2주' + ,tr: 'Son 2 hafta' ,zh_cn: '过去2周' } ,'Last month' : { @@ -581,6 +629,7 @@ function init() { ,hr: 'Protekli mjesec' ,sv: 'Senaste månaden' ,it: 'Mese scorso' + ,ja: '直近の1ヶ月' ,dk: 'Sidste måned' ,fi: 'Viime kuu' ,nb: 'Siste måned' @@ -590,6 +639,7 @@ function init() { ,sk: 'Posledný mesiac' ,nl: 'Afgelopen maand' ,ko: '지난달' + ,tr: 'Geçen Ay' ,zh_cn: '上个月' } ,'Last 3 months' : { @@ -604,6 +654,7 @@ function init() { ,hr: 'Protekla 3 mjeseca' ,sv: 'Senaste 3 månaderna' ,it: 'Ultimi 3 mesi' + ,ja: '直近の3ヶ月' ,dk: 'Sidste 3 måneder' ,fi: 'Viimeiset 3 kuukautta' ,nb: 'Siste 3 måneder' @@ -613,8 +664,84 @@ function init() { ,sk: 'Posledné 3 mesiace' ,nl: 'Afgelopen 3 maanden' ,ko: '지난 3달' + ,tr: 'Son 3 ay' ,zh_cn: '过去3个月' } + , 'between': { + cs: 'between' + ,de: 'between' + ,es: 'between' + ,fr: 'between' + ,el: 'between' + ,pt: 'between' + ,sv: 'between' + ,ro: 'between' + ,bg: 'between' + ,hr: 'between' + ,it: 'between' + ,ja: 'between' + ,dk: 'between' + ,fi: 'between' + ,nb: 'between' + ,he: 'between' + ,pl: 'between' + ,ru: 'between' + ,sk: 'between' + ,nl: 'between' + ,ko: 'between' + ,tr: 'between' + ,zh_cn: 'between' + } + , 'around': { + cs: 'around' + ,de: 'around' + ,es: 'around' + ,fr: 'around' + ,el: 'around' + ,pt: 'around' + ,sv: 'around' + ,ro: 'around' + ,bg: 'around' + ,hr: 'around' + ,it: 'around' + ,ja: 'around' + ,dk: 'around' + ,fi: 'around' + ,nb: 'around' + ,he: 'around' + ,pl: 'around' + ,ru: 'around' + ,sk: 'around' + ,nl: 'around' + ,ko: 'around' + ,tr: 'around' + ,zh_cn: 'around' + } + , 'and': { + cs: 'and' + ,de: 'and' + ,es: 'and' + ,fr: 'and' + ,el: 'and' + ,pt: 'and' + ,sv: 'and' + ,ro: 'and' + ,bg: 'and' + ,hr: 'and' + ,it: 'and' + ,ja: 'and' + ,dk: 'and' + ,fi: 'and' + ,nb: 'and' + ,he: 'and' + ,pl: 'and' + ,ru: 'and' + ,sk: 'and' + ,nl: 'and' + ,ko: 'and' + ,tr: 'and' + ,zh_cn: 'and' + } ,'From' : { cs: 'Od' ,de: 'Von' @@ -627,6 +754,7 @@ function init() { ,bg: 'От' ,hr: 'Od' ,it: 'Da' + ,ja: '開始日' ,dk: 'Fra' ,fi: 'Alkaen' ,nb: 'Fra' @@ -636,6 +764,7 @@ function init() { ,sk: 'Od' ,nl: 'Van' ,ko: '시작일' + ,tr: 'Başlangıç' ,zh_cn: '从' } ,'To' : { @@ -650,6 +779,7 @@ function init() { ,hr: 'Do' ,sv: 'Till' ,it: 'A' + ,ja: '終了日' ,dk: 'Til' ,fi: 'Asti' ,nb: 'Til' @@ -659,6 +789,7 @@ function init() { ,sk: 'Do' ,nl: 'Tot' ,ko: '종료일' + ,tr: 'Bitiş' ,zh_cn: '到' } ,'Notes' : { @@ -673,6 +804,7 @@ function init() { ,bg: 'Бележки' ,hr: 'Bilješke' ,it: 'Note' + ,ja: 'メモ' ,dk: 'Noter' ,fi: 'Merkinnät' ,nb: 'Notater' @@ -682,6 +814,7 @@ function init() { ,sk: 'Poznámky' ,nl: 'Notities' ,ko: '메모' + ,tr: 'Not' ,zh_cn: '记录' } ,'Food' : { @@ -696,6 +829,7 @@ function init() { ,bg: 'Храна' ,hr: 'Hrana' ,it: 'Cibo' + ,ja: '食事' ,dk: 'Mad' ,fi: 'Ruoka' ,nb: 'Mat' @@ -705,6 +839,7 @@ function init() { ,sk: 'Jedlo' ,nl: 'Voeding' ,ko: '음식' + ,tr: 'Gıda' ,zh_cn: '食物' } ,'Insulin' : { @@ -719,6 +854,7 @@ function init() { ,hr: 'Inzulin' ,sv: 'Insulin' ,it: 'Insulina' + ,ja: 'インスリン' ,dk: 'Insulin' ,fi: 'Insuliini' ,nb: 'Insulin' @@ -728,6 +864,7 @@ function init() { ,sk: 'Inzulín' ,nl: 'Insuline' ,ko: '인슐린' + ,tr: 'İnsülin' ,zh_cn: '胰岛素' } ,'Carbs' : { @@ -742,6 +879,7 @@ function init() { ,hr: 'Ugljikohidrati' ,sv: 'Kolhydrater' ,it: 'Carboidrati' + ,ja: '炭水化物' ,dk: 'Kulhydrater' ,fi: 'Hiilihydraatit' ,nb: 'Karbohydrater' @@ -751,6 +889,7 @@ function init() { ,sk: 'Sacharidy' ,nl: 'Koolhydraten' ,ko: '탄수화물' + ,tr: 'Karbonhidrat' ,zh_cn: '碳水化合物' } ,'Notes contain' : { @@ -766,15 +905,16 @@ function init() { ,hr: 'Sadržaj bilješki' ,sv: 'Notering innehåller' ,it: 'Contiene note' + ,ja: 'メモ内容' ,dk: 'Noter indeholder' ,fi: 'Merkinnät sisältävät' ,nb: 'Notater inneholder' - ,he: 'הערות מכילות' ,pl: 'Zawierają uwagi' ,ru: 'Примечания содержат' ,sk: 'Poznámky obsahujú' ,nl: 'Inhoud aantekening' ,ko: '메모 포함' + ,tr: 'Notlar içerir' ,zh_cn: '记录包括' } ,'Target bg range bottom' : { @@ -789,6 +929,7 @@ function init() { ,hr: 'Ciljna donja granica GUK-a' ,sv: 'Gräns för nedre blodsockervärde' ,it: 'Limite inferiore della glicemia' + ,ja: '目標血糖値 下限' ,dk: 'Nedre grænse for blodsukkerværdier' ,fi: 'Tavoitealueen alaraja' ,nb: 'Nedre grense for blodsukkerverdier' @@ -798,6 +939,7 @@ function init() { ,sk: 'Cieľová glykémia spodná' ,nl: 'Ondergrens doelbereik glucose' ,ko: '최저 목표 혈당 범위' + ,tr: 'Hedef KŞ aralığı düşük' ,zh_cn: '目标血糖范围 下限' } ,'top' : { @@ -812,6 +954,7 @@ function init() { ,hr: 'Gornja' ,sv: 'Toppen' ,it: 'Superiore' + ,ja: '上限' ,dk: 'Top' ,fi: 'yläraja' ,nb: 'Topp' @@ -821,6 +964,7 @@ function init() { ,sk: 'horná' ,nl: 'Top' ,ko: '최고치' + ,tr: 'Üstü' ,zh_cn: '上限' } ,'Show' : { @@ -835,6 +979,7 @@ function init() { ,bg: 'Покажи' ,hr: 'Prikaži' ,it: 'Mostra' + ,ja: '作成' ,dk: 'Vis' ,fi: 'Näytä' ,nb: 'Vis' @@ -843,7 +988,8 @@ function init() { ,ru: 'Показать' ,sk: 'Ukáž' ,nl: 'Laat zien' - ,ko: '보여 주세요~' + ,ko: '확인' + ,tr: 'Göster' ,zh_cn: '生成' } ,'Display' : { @@ -858,6 +1004,7 @@ function init() { ,hr: 'Prikaži' ,sv: 'Visa' ,it: 'Schermo' + ,ja: '表示' ,dk: 'Vis' ,fi: 'Näyttö' ,nb: 'Vis' @@ -867,6 +1014,7 @@ function init() { ,sk: 'Zobraz' ,nl: 'Weergeven' ,ko: '출력' + ,tr: 'Görüntüle' ,zh_cn: '显示' } ,'Loading' : { @@ -881,6 +1029,7 @@ function init() { ,hr: 'Učitavanje' ,sv: 'Laddar' ,it: 'Carico' + ,ja: 'ロード中' ,dk: 'Indlæser' ,fi: 'Lataan' ,nb: 'Laster' @@ -890,6 +1039,7 @@ function init() { ,sk: 'Nahrávam' ,nl: 'Laden' ,ko: '로딩' + ,tr: 'Yükleniyor' ,zh_cn: '载入中' } ,'Loading profile' : { @@ -904,6 +1054,7 @@ function init() { ,bg: 'Зареждане на профил' ,hr: 'Učitavanje profila' ,it: 'Carico il profilo' + ,ja: 'プロフィールロード中' ,dk: 'Indlæser profil' ,fi: 'Lataan profiilia' ,nb: 'Leser profil' @@ -913,6 +1064,7 @@ function init() { ,sk: 'Nahrávam profil' ,nl: 'Profiel laden' ,ko: '프로파일 로딩' + ,tr: 'Profil yükleniyor' ,zh_cn: '载入配置文件' } ,'Loading status' : { @@ -927,15 +1079,17 @@ function init() { ,bg: 'Зареждане на статус' ,hr: 'Učitavanje statusa' ,it: 'Stato di caricamento' + ,ja: 'ステータスロード中' ,dk: 'Indlæsnings status' ,fi: 'Lataan tilaa' ,nb: 'Leser status' ,he: 'טוען סטטוס' ,pl: 'Status załadowania' - ,ru: 'Загрузка статус' + ,ru: 'Загрузка состояния' ,sk: 'Nahrávam status' ,nl: 'Laadstatus' ,ko: '상태 로딩' + ,tr: 'Durum Yükleniyor' ,zh_cn: '载入状态' } ,'Loading food database' : { @@ -950,6 +1104,7 @@ function init() { ,bg: 'Зареждане на данни с храни' ,hr: 'Učitavanje baze podataka o hrani' ,it: 'Carico database alimenti' + ,ja: '食事データベースロード中' ,dk: 'Indlæser mad database' ,fi: 'Lataan ruokatietokantaa' ,nb: 'Leser matdatabase' @@ -959,6 +1114,7 @@ function init() { ,sk: 'Nahrávam databázu jedál' ,nl: 'Voeding database laden' ,ko: '음식 데이터 베이스 로딩' + ,tr: 'Gıda veritabanı yükleniyor' ,zh_cn: '载入食物数据库' } ,'not displayed' : { @@ -973,15 +1129,17 @@ function init() { ,hr: 'Ne prikazuje se' ,sv: 'Visas ej' ,it: 'Non visualizzato' + ,ja: '表示できません' ,dk: 'Vises ikke' ,fi: 'ei näytetä' ,nb: 'Vises ikke' ,he: 'לא מוצג' ,pl: 'Nie jest wyświetlany' - ,ru: 'Не изображено' + ,ru: 'Не отражено' ,sk: 'Nie je zobrazené' ,nl: 'Niet weergegeven' ,ko: '출력되지 않음' + ,tr: 'görüntülenmedi' ,zh_cn: '未显示' } ,'Loading CGM data of' : { @@ -996,6 +1154,7 @@ function init() { ,bg: 'Зареждане на CGM данни от' ,hr: 'Učitavanja podataka CGM-a' ,it: 'Carico dati CGM' + ,ja: 'CGMデータロード中' ,dk: 'Indlæser CGM-data for' ,fi: 'Lataan sensoritietoja: ' ,nb: 'Leser CGM-data for' @@ -1005,6 +1164,7 @@ function init() { ,sk: 'Nahrávam CGM dáta' ,nl: 'CGM data laden van' ,ko: 'CGM 데이터 로딩' + ,tr: 'den CGM veriler yükleniyor' ,zh_cn: '载入CGM(连续血糖监测)数据从' } ,'Loading treatments data of' : { @@ -1019,6 +1179,7 @@ function init() { ,bg: 'Зареждане на въведените лечения от' ,hr: 'Učitavanje podataka o tretmanu' ,it: 'Carico dati dei trattamenti' + ,ja: '治療データロード中' ,dk: 'Indlæser behandlingsdata for' ,fi: 'Lataan toimenpidetietoja: ' ,nb: 'Leser behandlingsdata for' @@ -1028,6 +1189,7 @@ function init() { ,sk: 'Nahrávam dáta ošetrenia' ,nl: 'Behandel gegevens laden van' ,ko: '처리 데이터 로딩' + ,tr: 'dan Tedavi verilerini yükle' ,zh_cn: '载入操作数据从' } ,'Processing data of' : { @@ -1042,15 +1204,17 @@ function init() { ,bg: 'Зареждане на данни от' ,hr: 'Obrada podataka' ,it: 'Elaborazione dei dati' + ,ja: 'データ処理中の日付:' ,dk: 'Behandler data for' ,fi: 'Käsittelen tietoja: ' ,nb: 'Behandler data for' ,he: 'מעבד נתונים של' ,pl: 'Przetwarzanie danych' - ,ru: 'Обработка данных' + ,ru: 'Обработка данных от' ,sk: 'Spracovávam dáta' ,nl: 'Gegevens verwerken van' ,ko: '데이터 처리 중' + ,tr: 'dan Veri işleme' ,zh_cn: '处理数据从' } ,'Portion' : { @@ -1065,6 +1229,7 @@ function init() { ,hr: 'Dio' ,sv: 'Portion' ,it: 'Porzione' + ,ja: '一食分' ,dk: 'Portion' ,fi: 'Annos' ,nb: 'Porsjon' @@ -1074,6 +1239,7 @@ function init() { ,sk: 'Porcia' ,nl: 'Portie' ,ko: '부분' + ,tr: 'Porsiyon' ,zh_cn: '部分' } ,'Size' : { @@ -1088,6 +1254,7 @@ function init() { ,bg: 'Големина' ,hr: 'Veličina' ,it: 'Formato' + ,ja: '量' ,dk: 'Størrelse' ,fi: 'Koko' ,nb: 'Størrelse' @@ -1097,6 +1264,7 @@ function init() { ,sk: 'Veľkosť' ,nl: 'Grootte' ,ko: '크기' + ,tr: 'Boyut' ,zh_cn: '大小' } ,'(none)' : { @@ -1111,15 +1279,17 @@ function init() { ,bg: '(няма)' ,hr: '(Prazno)' ,it: '(Nessuno)' + ,ja: '(データなし)' ,dk: '(ingen)' ,fi: '(tyhjä)' ,nb: '(ingen)' ,he: '(ללא)' ,pl: '(brak)' - ,ru: '(отсутствует)' + ,ru: '(нет)' ,sk: '(žiadny)' ,nl: '(geen)' ,ko: '(없음)' + ,tr: '(hiç)' ,zh_cn: '(无)' ,zh_tw: '(無)' } @@ -1135,15 +1305,17 @@ function init() { ,bg: 'няма' ,hr: 'Prazno' ,it: 'Nessuno' + ,ja: 'データなし' ,dk: 'Ingen' ,fi: 'tyhjä' ,nb: 'ingen' ,he: 'ללא' ,pl: 'brak' - ,ru: 'Отсутствует' + ,ru: 'Нет' ,sk: 'Žiadny' ,nl: 'geen' ,ko: '없음' + ,tr: 'Hiç' ,zh_cn: '无' ,zh_tw: '無' } @@ -1159,15 +1331,17 @@ function init() { ,bg: '<няма>' ,hr: '' ,it: '' + ,ja: '<データなし>' ,dk: '' ,fi: '' ,nb: '' ,he: '<ללא>' ,pl: '' - ,ru: '<отсутствует>' + ,ru: '<нет>' ,sk: '<žiadny>' ,nl: '' ,ko: '<없음>' + ,tr: '' ,zh_cn: '<无>' } ,'Result is empty' : { @@ -1182,6 +1356,7 @@ function init() { ,bg: 'Няма резултат' ,hr: 'Prazan rezultat' ,it: 'Risultato vuoto' + ,ja: '結果がありません' ,dk: 'Tomt resultat' ,fi: 'Ei tuloksia' ,nb: 'Tomt resultat' @@ -1191,6 +1366,7 @@ function init() { ,sk: 'Prázdny výsledok' ,nl: 'Geen resultaat' ,ko: '결과 없음' + ,tr: 'Sonuç boş' ,zh_cn: '结果为空' } ,'Day to day' : { @@ -1205,17 +1381,42 @@ function init() { ,bg: 'Ден за ден' ,hr: 'Svakodnevno' ,it: 'Giorno per giorno' + ,ja: '日差' ,dk: 'Dag til dag' ,fi: 'Päivittäinen' ,nb: 'Dag til dag' ,he: 'יום ביומו' ,pl: 'Dzień po dniu' - ,ru: 'Ежедневно' + ,ru: 'По дням' ,sk: 'Deň po dni' ,nl: 'Dag tot Dag' - ,ko: '날짜' + ,ko: '일별 그래프' + ,tr: 'Günden Güne' ,zh_cn: '日到日' } + ,'Week to week' : { + cs: 'Week to week' + ,de: 'Week to week' + ,es: 'Week to week' + ,fr: 'Week to week' + ,el: 'Week to week' + ,pt: 'Week to week' + ,sv: 'Week to week' + ,ro: 'Week to week' + ,bg: 'Week to week' + ,hr: 'Tjedno' + ,it: 'Week to week' + ,dk: 'Week to week' + ,fi: 'Week to week' + ,nb: 'Week to week' + ,he: 'Week to week' + ,pl: 'Week to week' + ,ru: 'По неделям' + ,sk: 'Week to week' + ,nl: 'Week to week' + ,ko: '주별 그래프' + ,zh_cn: 'Week to week' + } ,'Daily Stats' : { cs: 'Denní statistiky' ,de: 'Tägliche Statistik' @@ -1228,6 +1429,7 @@ function init() { ,bg: 'Дневна статистика' ,hr: 'Dnevna statistika' ,it: 'Statistiche giornaliere' + ,ja: '1日統計' ,dk: 'Daglig statistik' ,fi: 'Päivittäiset tilastot' ,nb: 'Daglig statistikk' @@ -1236,7 +1438,8 @@ function init() { ,ru: 'Ежедневная статистика' ,sk: 'Denné štatistiky' ,nl: 'Dagelijkse statistiek' - ,ko: '날짜 통계' + ,ko: '일간 통계' + ,tr: 'Günlük İstatistikler' ,zh_cn: '每日状态' } ,'Percentile Chart' : { @@ -1248,18 +1451,20 @@ function init() { ,pt: 'Percentis' ,ro: 'Grafic percentile' ,bg: 'Процентна графика' - ,hr: 'Tablica u postotcima' + ,hr: 'Percentili' ,sv: 'Procentgraf' ,it: 'Grafico percentile' + ,ja: 'パーセント図' ,dk: 'Procentgraf' ,fi: 'Suhteellinen kuvaaja' ,nb: 'Prosentgraf' ,he: 'טבלת עשירונים' ,pl: 'Wykres percentyl' - ,ru: 'Перцентильная диаграмма' + ,ru: 'Процентильная диаграмма' ,sk: 'Percentil' ,nl: 'Procentuele grafiek' ,ko: '백분위 그래프' + ,tr: 'Yüzdelik Grafiği' ,zh_cn: '百分位图形' } ,'Distribution' : { @@ -1274,6 +1479,7 @@ function init() { ,hr: 'Distribucija' ,sv: 'Distribution' ,it: 'Distribuzione' + ,ja: '配分' ,dk: 'Distribution' ,fi: 'Jakauma' ,nb: 'Distribusjon' @@ -1283,6 +1489,7 @@ function init() { ,sk: 'Distribúcia' ,nl: 'Verdeling' ,ko: '분포' + ,tr: 'Dağılım' ,zh_cn: '分布' } ,'Hourly stats' : { @@ -1297,6 +1504,7 @@ function init() { ,bg: 'Статистика по часове' ,hr: 'Statistika po satu' ,it: 'Statistiche per ore' + ,ja: '1時間統計' ,dk: 'Timestatistik' ,fi: 'Tunneittainen tilasto' ,nb: 'Timestatistikk' @@ -1305,29 +1513,35 @@ function init() { ,ru: 'Почасовая статистика' ,sk: 'Hodinové štatistiky' ,nl: 'Statistieken per uur' - ,ko: '시간 통계' + ,ko: '시간대별 통계' + ,tr: 'Saatlik istatistikler' ,zh_cn: '每小时状态' } ,'netIOB stats': { // hourlystats.js nl: 'netIOB stats' ,sv: 'netIOB statistik' + ,dk: 'netIOB statistik' ,he: 'netIOB סטטיסטיקת' ,de: 'netIOB Statistiken' ,fi: 'netIOB tilasto' ,bg: 'netIOB татистика' + ,hr: 'netIOB statistika' + ,ru: 'статистика нетто активн инс netIOB' + ,tr: 'netIOB istatistikleri' } ,'temp basals must be rendered to display this report': { //hourlystats.js nl: 'tijdelijk basaal moet zichtbaar zijn voor dit rapport' ,sv: 'temp basal måste vara synlig för denna rapport' ,de: 'temporäre Basalraten müssen für diesen Report sichtbar sein' ,fi: 'tämä raportti vaatii, että basaalien piirto on päällä' - ,he: 'חובה לאפשר רמה בזלית זמנית כדי לרות דוח זה' ,bg: 'временните базали трябва да са показани за да се покаже тази това' + ,hr: 'temp bazali moraju biti prikazani kako bi se vidio ovaj izvještaj' ,he: 'חובה לאפשר רמה בזלית זמנית כדי לרות דוח זה' + ,ru: 'для этого отчета требуется прорисовка врем базалов' + ,tr: 'Bu raporu görüntülemek için geçici bazal oluşturulmalıdır' } ,'Weekly success' : { cs: 'Statistika po týdnech' - ,he: 'הצלחה שבועית' ,de: 'Wöchentlicher Erfolg' ,es: 'Resultados semanales' ,fr: 'Résultat hebdomadaire' @@ -1338,6 +1552,7 @@ function init() { ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' ,it: 'Statistiche settimanali' + ,ja: '1週間統計' ,dk: 'Uge resultat' ,fi: 'Viikkotilasto' ,he: 'הצלחה שבועית' @@ -1347,6 +1562,7 @@ function init() { ,sk: 'Týždenná úspešnosť' ,nl: 'Wekelijkse successen' ,ko: '주간 통계' + ,tr: 'Haftalık başarı' ,zh_cn: '每周统计' } ,'No data available' : { @@ -1361,15 +1577,17 @@ function init() { ,hr: 'Nema raspoloživih podataka' ,sv: 'Data saknas' ,it: 'Dati non disponibili' + ,ja: '利用できるデータがありません' ,dk: 'Mangler data' ,fi: 'Tietoja ei saatavilla' ,nb: 'Mangler data' ,he: 'אין מידע זמין' ,pl: 'Brak danych' - ,ru: 'Нет доступных данных' + ,ru: 'Нет данных' ,sk: 'Žiadne dostupné dáta' ,nl: 'Geen gegevens beschikbaar' ,ko: '활용할 수 있는 데이터 없음' + ,tr: 'Veri yok' ,zh_cn: '无可用数据' } ,'Low' : { @@ -1384,15 +1602,17 @@ function init() { ,bg: 'Ниска' ,hr: 'Nizak' ,it: 'Basso' + ,ja: '目標血糖値低値' ,dk: 'Lav' ,fi: 'Matala' ,nb: 'Lav' ,he: 'נמוך' ,pl: 'Niski' - ,ru: 'Низкий СК' + ,ru: 'Низкая ГК' ,sk: 'Nízka' ,nl: 'Laag' ,ko: '낮음' + ,tr: 'Düşük' ,zh_cn: '低血糖' } ,'In Range' : { @@ -1407,6 +1627,7 @@ function init() { ,bg: 'В граници' ,hr: 'U rasponu' ,it: 'Nell\'intervallo' + ,ja: '目標血糖値範囲内' ,dk: 'Indenfor intervallet' ,fi: 'Tavoitealueella' ,nb: 'Innenfor intervallet' @@ -1416,6 +1637,7 @@ function init() { ,sk: 'V rozsahu' ,nl: 'Binnen bereik' ,ko: '범위 안 ' + ,tr: 'Hedef alanında' ,zh_cn: '范围内' } ,'Period' : { @@ -1430,6 +1652,7 @@ function init() { ,bg: 'Период' ,hr: 'Period' ,it: 'Periodo' + ,ja: '期間' ,dk: 'Periode' ,fi: 'Aikaväli' ,nb: 'Periode' @@ -1439,6 +1662,7 @@ function init() { ,sk: 'Obdobie' ,nl: 'Periode' ,ko: '기간 ' + ,tr: 'Periyot' ,zh_cn: '期间' } ,'High' : { @@ -1453,15 +1677,17 @@ function init() { ,bg: 'Висока' ,hr: 'Visok' ,it: 'Alto' + ,ja: '目標血糖値高値' ,dk: 'Høj' ,fi: 'Korkea' ,nb: 'Høy' ,he: 'גבוה' ,pl: 'Wysoki' - ,ru: 'Высокий СК' + ,ru: 'Высокая ГК' ,sk: 'Vysoká' ,nl: 'Hoog' ,ko: '높음' + ,tr: 'Yüksek' ,zh_cn: '高血糖' } ,'Average' : { @@ -1476,15 +1702,17 @@ function init() { ,bg: 'Средна' ,hr: 'Prosjek' ,it: 'Media' + ,ja: '平均値' ,dk: 'Gennemsnit' ,fi: 'Keskiarvo' ,nb: 'Gjennomsnitt' ,he: 'ממוצע' ,pl: 'Średnia' - ,ru: 'Усредненный СК' + ,ru: 'Средняя' ,sk: 'Priemer' ,nl: 'Gemiddeld' ,ko: '평균' + ,tr: 'Ortalama' ,zh_cn: '平均' } ,'Low Quartile' : { @@ -1499,6 +1727,7 @@ function init() { ,hr: 'Donji kvartil' ,sv: 'Nedre kvadranten' ,it: 'Quartile basso' + ,ja: '下四分位値' ,dk: 'Nedre kvartil' ,fi: 'Alin neljäsosa' ,nb: 'Nedre kvartil' @@ -1508,6 +1737,7 @@ function init() { ,sk: 'Nizky kvartil' ,nl: 'Eerste kwartiel' ,ko: '낮은 4분위' + ,tr: 'Alt Çeyrek' ,zh_cn: '下四分位数' } ,'Upper Quartile' : { @@ -1522,6 +1752,7 @@ function init() { ,hr: 'Gornji kvartil' ,sv: 'Övre kvadranten' ,it: 'Quartile alto' + ,ja: '高四分位値' ,dk: 'Øvre kvartil' ,fi: 'Ylin neljäsosa' ,nb: 'Øvre kvartil' @@ -1531,6 +1762,7 @@ function init() { ,sk: 'Vysoký kvartil' ,nl: 'Derde kwartiel' ,ko: '높은 4분위' + ,tr: 'Üst Çeyrek' ,zh_cn: '上四分位数' } ,'Quartile' : { @@ -1545,6 +1777,7 @@ function init() { ,hr: 'Kvartil' ,sv: 'Kvadrant' ,it: 'Quartile' + ,ja: '四分位値' ,dk: 'Kvartil' ,fi: 'Neljäsosa' ,nb: 'Kvartil' @@ -1554,6 +1787,7 @@ function init() { ,sk: 'Kvartil' ,nl: 'Kwartiel' ,ko: '4분위' + ,tr: 'Çeyrek' ,zh_cn: '四分位数' } ,'Date' : { @@ -1568,6 +1802,7 @@ function init() { ,bg: 'Дата' ,hr: 'Datum' ,it: 'Data' + ,ja: '日付' ,dk: 'Dato' ,fi: 'Päivämäärä' ,nb: 'Dato' @@ -1577,13 +1812,13 @@ function init() { ,sk: 'Dátum' ,nl: 'Datum' ,ko: '날짜' + ,tr: 'Tarih' ,zh_cn: '日期' } ,'Normal' : { cs: 'Normální' ,de: 'Normal' ,es: 'Normal' - ,he: 'נורמלי ' ,fr: 'Normale' ,el: 'Εντός Στόχου' ,pt: 'Normal' @@ -1592,6 +1827,7 @@ function init() { ,bg: 'Нормалнa' ,hr: 'Normalno' ,it: 'Normale' + ,ja: '通常' ,dk: 'Normal' ,fi: 'Normaali' ,nb: 'Normal' @@ -1601,6 +1837,7 @@ function init() { ,sk: 'Normálny' ,nl: 'Normaal' ,ko: '보통' + ,tr: 'Normal' ,zh_cn: '正常' } ,'Median' : { @@ -1615,6 +1852,7 @@ function init() { ,hr: 'Srednje' ,sv: 'Median' ,it: 'Mediana' + ,ja: '中央値' ,dk: 'Median' ,fi: 'Mediaani' ,nb: 'Median' @@ -1624,6 +1862,7 @@ function init() { ,sk: 'Medián' ,nl: 'Mediaan' ,ko: '중간값' + ,tr: 'Orta Değer' ,zh_cn: '中值' } ,'Readings' : { @@ -1638,15 +1877,17 @@ function init() { ,bg: 'Измервания' ,hr: 'Vrijednosti' ,it: 'Valori' - ,dk: 'Aflæsning' + ,ja: '読み込み' + ,dk: 'Aflæsninger' ,fi: 'Lukemia' ,nb: 'Avlesning' ,he: 'קריאות' ,pl: 'Odczyty' - ,ru: 'Измерения' + ,ru: 'Значения' ,sk: 'Záznamy' ,nl: 'Metingen' ,ko: '혈당' + ,tr: 'Ölçüm' ,zh_cn: '读数' } ,'StDev' : { @@ -1661,6 +1902,7 @@ function init() { ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' ,it: 'Dev.std' + ,ja: '標準偏差' ,dk: 'Standard afvigelse' ,fi: 'Keskijakauma' ,nb: 'Standardavvik' @@ -1670,6 +1912,7 @@ function init() { ,ru: 'Стандартное отклонение' ,sk: 'Štand. odch.' ,ko: '표준 편차' + ,tr: 'Standart Sapma' ,zh_cn: '标准偏差' } ,'Daily stats report' : { @@ -1684,20 +1927,21 @@ function init() { ,hr: 'Izvješće o dnevnim statistikama' ,sv: 'Dygnsstatistik' ,it: 'Statistiche giornaliere' + ,ja: '1日ごとの統計のレポート' ,dk: 'Daglig statistik rapport' ,fi: 'Päivittäinen tilasto' ,nb: 'Daglig statistikkrapport' ,he: 'דוח סטטיסטיקה יומית' ,pl: 'Dzienne statystyki' - ,ru: 'Статистика дня' + ,ru: 'Суточная статистика' ,sk: 'Denné štatistiky' ,nl: 'Dagelijkse statistieken' - ,ko: '날짜 통계 보고서' + ,ko: '일간 통계 보고서' + ,tr: 'Günlük istatistikler raporu' ,zh_cn: '每日状态报表' } ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' - ,he: 'דוח אחוזוני גלוקוזה' ,de: 'Glukose-Perzentil Bericht' ,es: 'Informe de percetiles de glucemia' ,fr: 'Rapport percentiles Glycémie' @@ -1708,15 +1952,17 @@ function init() { ,bg: 'Графика на КЗ' ,hr: 'Izvješće o postotku GUK-a' ,it: 'Percentuale Glicemie' + ,ja: 'グルコース(%)レポート' ,dk: 'Glukoserapport i procent' ,fi: 'Verensokeriarvojen jakauma' ,nb: 'Glukoserapport i prosent' ,he: 'דוח אחוזון סוכר' ,pl: 'Tabela centylowa glikemii' - ,ru: 'Процентиль' + ,ru: 'Процентильная ГК' ,sk: 'Report percentilu glykémií' ,nl: 'Glucose percentiel rapport' ,ko: '혈당 백분위 보고서' + ,tr: 'Glikoz Yüzdelik raporu' ,zh_cn: '血糖百分位报表' } ,'Glucose distribution' : { @@ -1731,15 +1977,17 @@ function init() { ,hr: 'Distribucija GUK-a' ,sv: 'Glukosdistribution' ,it: 'Distribuzione glicemie' + ,ja: '血糖値の分布' ,dk: 'Glukosefordeling' ,fi: 'Glukoosijakauma' ,nb: 'Glukosefordeling' ,he: 'התפלגות סוכר' ,pl: 'Rozkład glikemii' - ,ru: 'Распределение СК' + ,ru: 'Распределение ГК' ,sk: 'Rozloženie glykémie' ,nl: 'Glucose verdeling' ,ko: '혈당 분포' + ,tr: 'Glikoz dağılımı' ,zh_cn: '血糖分布' } ,'days total' : { @@ -1754,6 +2002,7 @@ function init() { ,bg: 'общо за деня' ,hr: 'ukupno dana' ,it: 'Giorni totali' + ,ja: '合計日数' ,dk: 'antal dage' ,fi: 'päivän arvio' ,nb: 'antall dager' @@ -1762,7 +2011,8 @@ function init() { ,ru: 'всего дней' ,sk: 'dní celkom' ,nl: 'Totaal dagen' - ,ko: '날짜 전체' + ,ko: '일 전체' + ,tr: 'toplam gün' ,zh_cn: '天总计' } ,'Total per day' : { @@ -1775,17 +2025,18 @@ function init() { ,sv: 'antal dagar' ,ro: 'total zile' ,bg: 'общо за деня' - ,hr: 'ukupno dana' + ,hr: 'Ukupno po danu' ,it: 'Giorni totali' ,dk: 'antal dage' ,fi: 'päivän arvio' ,nb: 'antall dager' ,he: 'מספר ימים' ,pl: 'dni łącznie' - ,ru: 'всего дней' + ,ru: 'всего за сутки' ,sk: 'dní celkom' ,nl: 'Totaal dagen' - ,ko: '날짜 전체' + ,ko: '하루 총량' + ,tr: 'Günlük toplam' ,zh_cn: '天总计' } ,'Overall' : { @@ -1798,9 +2049,10 @@ function init() { ,sv: 'Genomsnitt' ,ro: 'General' ,bg: 'Общо' - ,hr: 'Ukupno' + ,hr: 'Sveukupno' ,it: 'Generale' - ,dk: 'Overall' + ,ja: '総合' + ,dk: 'Gennemsnit' ,fi: 'Yhteenveto' ,nb: 'Generelt' ,he: 'סך הכל' @@ -1809,6 +2061,7 @@ function init() { ,sk: 'Súhrn' ,nl: 'Totaal' ,ko: '전체' + ,tr: 'Tüm' ,zh_cn: '概览' } ,'Range' : { @@ -1823,6 +2076,7 @@ function init() { ,bg: 'Диапазон' ,hr: 'Raspon' ,it: 'Intervallo' + ,ja: '範囲' ,dk: 'Interval' ,fi: 'Alue' ,nb: 'Intervall' @@ -1832,6 +2086,7 @@ function init() { ,sk: 'Rozsah' ,nl: 'Bereik' ,ko: '범위' + ,tr: 'Alan' ,zh_cn: '范围' } ,'% of Readings' : { @@ -1846,6 +2101,7 @@ function init() { ,bg: '% от измервания' ,hr: '% očitanja' ,it: '% dei valori' + ,ja: '%精度' ,dk: '% af aflæsningerne' ,fi: '% lukemista' ,nb: '% af avlesningene' @@ -1854,7 +2110,8 @@ function init() { ,ru: '% измерений' ,sk: '% záznamov' ,nl: '% metingen' - ,ko: '% 읽는 중' + ,ko: '수신된 혈당 비율(%)' + ,tr: '% Okumaların' ,zh_cn: '%已读取' } ,'# of Readings' : { @@ -1869,15 +2126,17 @@ function init() { ,bg: '№ от измервания' ,hr: 'broj očitanja' ,it: '# di valori' - ,dk: 'Antal af aflæsninger' + ,ja: '#精度' + ,dk: 'Antal aflæsninger' ,fi: 'Lukemien määrä' ,nb: 'Antall avlesninger' ,he: 'מספר קריאות' ,pl: 'Ilość Odczytów' - ,ru: '№ измерения' + ,ru: 'кол-во измерений' ,sk: 'Počet záznamov' ,nl: 'Aantal metingen' - ,ko: '# 읽는 중' + ,ko: '수신된 혈당 개수(#)' + ,tr: '# Okumaların' ,zh_cn: '#已读取' } ,'Mean' : { @@ -1892,6 +2151,7 @@ function init() { ,bg: 'Средна стойност' ,hr: 'Prosjek' ,it: 'Media' + ,ja: '意味' ,dk: 'Gennemsnit' ,fi: 'Keskiarvo' ,nb: 'Gjennomsnitt' @@ -1901,6 +2161,7 @@ function init() { ,sk: 'Stred' ,nl: 'Gemiddeld' ,ko: '평균' + ,tr: 'ortalama' ,zh_cn: '平均' } ,'Standard Deviation' : { @@ -1915,6 +2176,7 @@ function init() { ,hr: 'Standardna devijacija' ,sv: 'Standardavvikelse' ,it: 'Deviazione Standard' + ,ja: '標準偏差' ,dk: 'Standardafvigelse' ,fi: 'Keskijakauma' ,nb: 'Standardavvik' @@ -1924,6 +2186,7 @@ function init() { ,sk: 'Štandardná odchylka' ,nl: 'Standaard deviatie' ,ko: '표준 편차' + ,tr: 'Standart Sapma' ,zh_cn: '标准偏差' } ,'Max' : { @@ -1938,6 +2201,7 @@ function init() { ,bg: 'Макс.' ,hr: 'Max' ,it: 'Max' + ,ja: '最大値' ,dk: 'Max' ,fi: 'Maks' ,nb: 'Max' @@ -1947,6 +2211,7 @@ function init() { ,sk: 'Max' ,nl: 'Max' ,ko: '최대값' + ,tr: 'Max' ,zh_cn: '最大值' } ,'Min' : { @@ -1961,6 +2226,7 @@ function init() { ,bg: 'Мин.' ,hr: 'Min' ,it: 'Min' + ,ja: '最小値' ,dk: 'Min' ,fi: 'Min' ,nb: 'Min' @@ -1970,6 +2236,7 @@ function init() { ,sk: 'Min' ,nl: 'Min' ,ko: '최소값' + ,tr: 'Min' ,zh_cn: '最小值' } ,'A1c estimation*' : { @@ -1984,6 +2251,7 @@ function init() { ,hr: 'Procjena HbA1c-a' ,sv: 'Beräknat A1c-värde ' ,it: 'Stima A1c' + ,ja: '予想HbA1c' ,dk: 'Beregnet A1c-værdi ' ,fi: 'A1c arvio*' ,nb: 'Beregnet HbA1c' @@ -1993,11 +2261,11 @@ function init() { ,sk: 'Odhadované HbA1C*' ,nl: 'Geschatte HbA1C' ,ko: '예상 당화혈 색소' + ,tr: 'Tahmini A1c *' ,zh_cn: '糖化血红蛋白估算' } ,'Weekly Success' : { cs: 'Týdenní úspěšnost' - ,he: 'הצלחה שבועית ' ,de: 'Wöchtlicher Erfolg' ,es: 'Resultados semanales' ,fr: 'Réussite hebdomadaire' @@ -2008,15 +2276,17 @@ function init() { ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' ,it: 'Risultati settimanali' + ,ja: '週間達成度' ,dk: 'Uge resultat' ,fi: 'Viikottainen tulos' ,nb: 'Ukeresultat' ,he: 'הצלחה שבועית' ,pl: 'Wyniki tygodniowe' - ,ru: 'Результаты недели' + ,ru: 'Итоги недели' ,sk: 'Týždenná úspešnosť' ,nl: 'Wekelijks succes' ,ko: '주간 통계' + ,tr: 'Haftalık Başarı' ,zh_cn: '每周统计' } ,'There is not sufficient data to run this report. Select more days.' : { @@ -2031,6 +2301,7 @@ function init() { ,hr: 'Nema dovoljno podataka za izvođenje izvještaja. Odaberite još dana.' ,sv: 'Data saknas för att köra rapport. Välj fler dagar.' ,it: 'Non ci sono dati sufficienti per eseguire questo rapporto. Selezionare più giorni.' + ,ja: 'レポートするためのデータが足りません。もっと多くの日を選択してください。' ,dk: 'Der er utilstrækkeligt data til at generere rapporten. Vælg flere dage.' ,fi: 'Raporttia ei voida luoda liian vähäisen tiedon vuoksi. Valitse useampia päiviä.' ,nb: 'Der er ikke nok data til å lage rapporten. Velg flere dager.' @@ -2040,6 +2311,7 @@ function init() { ,sk: 'Nedostatok dát. Vyberte dlhšie časové obdobie.' ,nl: 'Er zijn niet genoeg gegevens voor dit rapport, selecteer meer dagen.' ,ko: '이 보고서를 실행하기 위한 데이터가 충분하지 않습니다. 더 많은 날들을 선택해 주세요.' + ,tr: 'Bu raporu çalıştırmak için yeterli veri yok. Daha fazla gün seçin.' ,zh_cn: '没有足够的数据生成报表,请选择更长时间段。' } // food editor @@ -2056,21 +2328,21 @@ function init() { ,hr: 'Koristi se pohranjeni API tajni hash' ,sv: 'Använd hemlig API-nyckel' ,it: 'Stai utilizzando API hash segreta' + ,ja: '保存されたAPI secret hashを使用する' ,dk: 'Anvender gemt API-nøgle' ,fi: 'Tallennettu salainen API-tarkiste käytössä' ,nb: 'Bruker lagret API nøkkel' - ,he: 'עורך אוכל' ,pl: 'Korzystając z zapisanego poufnego hasha API' ,ru: 'Применение сохраненного пароля API' ,sk: 'Používam uložený API hash heslo' ,nl: 'Gebruik opgeslagen geheime API Hash' ,ko: '저장된 API secret hash를 사용 중' + ,tr: 'Kaydedilmiş API secret hash kullan' ,zh_cn: '使用已存储的API密钥哈希值' ,zh_tw: '使用已存儲的API密鑰哈希值' } ,'No API secret hash stored yet. You need to enter API secret.' : { cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' - ,he: 'הכנס את סיסמת ממשק תכנות יישומים הסודית' ,de: 'Keine API-Prüfsumme gespeichert. Bitte API-Prüfsumme eingeben.' ,es: 'No se ha almacenado ningún hash todavía. Debe introducir su secreto API.' ,fr: 'Pas de secret API existant. Vous devez en entrer un.' @@ -2081,6 +2353,7 @@ function init() { ,hr: 'Nema pohranjenog API tajnog hasha. Unesite tajni API' ,sv: 'Hemlig api-nyckel saknas. Du måste ange API hemlighet' ,it: 'API hash segreto non è ancora memorizzato. È necessario inserire API segreto.' + ,ja: 'API secret hashがまだ保存されていません。API secretの入力が必要です。' ,dk: 'Mangler API-nøgle. Du skal indtaste API nøglen' ,fi: 'Salainen API-tarkiste puuttuu. Syötä API tarkiste.' ,nb: 'Mangler API nøkkel. Du må skrive inn API hemmelighet.' @@ -2090,12 +2363,12 @@ function init() { ,sk: 'Nieje uložené žiadne API hash heslo. Musíte zadať API heslo.' ,nl: 'Er is nog geen geheime API Hash opgeslagen. U moet eerst een geheime API code invoeren.' ,ko: 'API secret hash가 아직 저장되지 않았습니다. API secret를 입력해 주세요.' + ,tr: 'Henüz bir API secret hash saklanmadı. API parolasını girmeniz gerekiyor.' ,zh_cn: '没有已存储的API密钥,请输入API密钥。' ,zh_tw: '沒有已存儲的API密鑰,請輸入API密鑰。' } ,'Database loaded' : { cs: 'Databáze načtena' - ,he: 'אגר מידע נטען ' ,de: 'Datenbank geladen' ,es: 'Base de datos cargada' ,fr: 'Base de données chargée' @@ -2106,6 +2379,7 @@ function init() { ,hr: 'Baza podataka je učitana' ,sv: 'Databas laddad' ,it: 'Database caricato' + ,ja: 'データベースロード完了' ,dk: 'Database indlæst' ,fi: 'Tietokanta ladattu' ,nb: 'Database lest' @@ -2115,6 +2389,7 @@ function init() { ,sk: 'Databáza načítaná' ,nl: 'Database geladen' ,ko: '데이터베이스 로드' + ,tr: 'Veritabanı yüklendi' ,zh_cn: '数据库已载入' } ,'Error: Database failed to load' : { @@ -2129,6 +2404,7 @@ function init() { ,hr: 'Greška: Baza podataka nije učitana' ,sv: 'Error: Databas kan ej laddas' ,it: 'Errore: database non è stato caricato' + ,ja: 'エラー:データベースを読み込めません' ,dk: 'Fejl: Database kan ikke indlæses' ,fi: 'Virhe: Tietokannan lataaminen epäonnistui' ,nb: 'Feil: Database kan ikke leses' @@ -2138,8 +2414,34 @@ function init() { ,sk: 'Chyba pri načítaní databázy' ,nl: 'FOUT: Database niet geladen' ,ko: '에러: 데이터베이스 로드 실패' + ,tr: 'Hata: Veritabanı yüklenemedi' ,zh_cn: '错误:数据库载入失败' } + ,'Error' : { + cs: 'Error' + ,he: 'Error' + ,nb: 'Error' + ,fr: 'Error' + ,ro: 'Error' + ,el: 'Error' + ,de: 'Error' + ,es: 'Error' + ,dk: 'Error' + ,sv: 'Error' + ,bg: 'Error' + ,hr: 'Greška' + ,it: 'Error' + ,fi: 'Error' + ,pl: 'Error' + ,pt: 'Error' + ,ru: 'Ошибка' + ,sk: 'Error' + ,nl: 'Error' + ,ko: 'Error' + ,tr: 'Error' + ,zh_cn: 'Error' + ,zh_tw: 'Error' + } ,'Create new record' : { cs: 'Vytvořit nový záznam' ,de: 'Erstelle neuen Datensatz' @@ -2152,7 +2454,8 @@ function init() { ,hr: 'Kreiraj novi zapis' ,sv: 'Skapa ny post' ,it: 'Crea nuovo registro' - ,dk: 'Danner ny post' + ,ja: '新しい記録を作る' + ,dk: 'Opret ny post' ,fi: 'Luo uusi tallenne' ,nb: 'Lager ny registrering' ,he: 'צור רשומה חדשה' @@ -2161,6 +2464,7 @@ function init() { ,sk: 'Vytovriť nový záznam' ,nl: 'Opslaan' ,ko: '새입력' + ,tr: 'Yeni kayıt oluştur' ,zh_cn: '新增记录' } ,'Save record' : { @@ -2175,6 +2479,7 @@ function init() { ,hr: 'Spremi zapis' ,sv: 'Spara post' ,it: 'Salva Registro' + ,ja: '保存' ,dk: 'Gemmer post' ,fi: 'Tallenna' ,nb: 'Lagrer registrering' @@ -2184,6 +2489,7 @@ function init() { ,sk: 'Uložiť záznam' ,nl: 'Sla op' ,ko: '저장' + ,tr: 'Kayıtları kaydet' ,zh_cn: '保存记录' } ,'Portions' : { @@ -2198,6 +2504,7 @@ function init() { ,hr: 'Dijelovi' ,sv: 'Portion' ,it: 'Porzioni' + ,ja: '一食分' ,dk: 'Portioner' ,fi: 'Annokset' ,nb: 'Porsjoner' @@ -2207,6 +2514,7 @@ function init() { ,sk: 'Porcií' ,nl: 'Porties' ,ko: '부분' + ,tr: 'Porsiyonlar' ,zh_cn: '部分' } ,'Unit' : { @@ -2221,6 +2529,7 @@ function init() { ,hr: 'Jedinica' ,sv: 'Enhet' ,it: 'Unità' + ,ja: '単位' ,dk: 'Enheder' ,fi: 'Yksikkö' ,nb: 'Enhet' @@ -2230,6 +2539,7 @@ function init() { ,sk: 'Jednot.' ,nl: 'Eenheid' ,ko: '단위' + ,tr: 'Birim' ,zh_cn: '单位' } ,'GI' : { @@ -2245,15 +2555,16 @@ function init() { ,bg: 'ГИ' ,hr: 'GI' ,it: 'IG-Ind.Glic.' + ,ja: 'GI' ,dk: 'GI' ,fi: 'GI' ,nb: 'GI' ,pl: 'IG' ,ru: 'ГИ' ,sk: 'GI' - ,he: 'GI' ,nl: 'Glycemische index ' ,ko: '혈당 지수' + ,tr: 'GI-Glisemik İndeks' ,zh_cn: 'GI(血糖生成指数)' } ,'Edit record' : { @@ -2268,6 +2579,7 @@ function init() { ,hr: 'Uredi zapis' ,sv: 'Editera post' ,it: 'Modifica registro' + ,ja: '記録編集' ,dk: 'Rediger post' ,fi: 'Muokkaa tallennetta' ,nb: 'Editere registrering' @@ -2276,7 +2588,8 @@ function init() { ,ru: 'Редактировать запись' ,sk: 'Upraviť záznam' ,nl: 'Bewerk invoer' - ,ko: '편집' + ,ko: '편집기록' + ,tr: 'Kaydı düzenle' ,zh_cn: '编辑记录' } ,'Delete record' : { @@ -2291,6 +2604,7 @@ function init() { ,hr: 'Izbriši zapis' ,sv: 'Radera post' ,it: 'Cancella registro' + ,ja: '記録削除' ,dk: 'Slet post' ,fi: 'Tuhoa tallenne' ,nb: 'Slette registrering' @@ -2299,7 +2613,8 @@ function init() { ,ru: 'Стереть запись' ,sk: 'Zmazať záznam' ,nl: 'Verwijder invoer' - ,ko: '삭제' + ,ko: '삭제기록' + ,tr: 'Kaydı sil' ,zh_cn: '删除记录' } ,'Move to the top' : { @@ -2314,6 +2629,7 @@ function init() { ,bg: 'Преместване в началото' ,hr: 'Premjesti na vrh' ,it: 'Spostare verso l\'alto' + ,ja: 'トップ画面へ' ,dk: 'Gå til toppen' ,fi: 'Siirrä ylimmäksi' ,nb: 'Gå til toppen' @@ -2323,6 +2639,7 @@ function init() { ,sk: 'Presunúť na začiatok' ,nl: 'Ga naar boven' ,ko: '맨처음으로 이동' + ,tr: 'En üste taşı' ,zh_cn: '移至顶端' } ,'Hidden' : { @@ -2337,6 +2654,7 @@ function init() { ,bg: 'Скрити' ,hr: 'Skriveno' ,it: 'Nascosto' + ,ja: '隠す' ,dk: 'Skjult' ,fi: 'Piilotettu' ,nb: 'Skjult' @@ -2346,6 +2664,7 @@ function init() { ,sk: 'Skrytý' ,nl: 'Verborgen' ,ko: '숨김' + ,tr: 'Gizli' ,zh_cn: '隐藏' } ,'Hide after use' : { @@ -2360,6 +2679,7 @@ function init() { ,hr: 'Sakrij nakon korištenja' ,sv: 'Dölj efter användning' ,it: 'Nascondi dopo l\'uso' + ,ja: '使用後に隠す' ,dk: 'Skjul efter brug' ,fi: 'Piilota käytön jälkeen' ,nb: 'Skjul etter bruk' @@ -2369,6 +2689,7 @@ function init() { ,sk: 'Skryť po použití' ,nl: 'Verberg na gebruik' ,ko: '사용 후 숨김' + ,tr: 'Kullandıktan sonra gizle' ,zh_cn: '使用后隐藏' } ,'Your API secret must be at least 12 characters long' : { @@ -2382,9 +2703,9 @@ function init() { ,ro: 'Cheia API trebuie să aibă mai mult de 12 caractere' ,bg: 'Вашата АPI парола трябва да е дълга поне 12 символа' ,hr: 'Vaš tajni API mora sadržavati barem 12 znakova' - ,he:' הסיסמא הסודית חייבת להיות באורך של 12 תווים לפחות' ,sv: 'Hemlig API-nyckel måsta innehålla 12 tecken' ,it: 'il vostro API secreto deve essere lungo almeno 12 caratteri' + ,ja: 'APIシークレットは12文字以上の長さが必要です' ,dk: 'Din API nøgle skal være mindst 12 tegn lang' ,fi: 'API-avaimen tulee olla ainakin 12 merkin mittainen' ,nb: 'Din API nøkkel må være minst 12 tegn lang' @@ -2393,6 +2714,7 @@ function init() { ,sk: 'Vaše API heslo musí mať najmenej 12 znakov' ,nl: 'Uw API wachtwoord dient tenminste 12 karakters lang te zijn' ,ko: 'API secret는 최소 12자 이상이여야 합니다.' + ,tr: 'PI parolanız en az 12 karakter uzunluğunda olmalıdır' ,zh_cn: 'API密钥最少需要12个字符' ,zh_tw: 'API密鑰最少需要12個字符' } @@ -2409,15 +2731,16 @@ function init() { ,hr: 'Neispravan tajni API' ,sv: 'Felaktig API-nyckel' ,it: 'API secreto non corretto' + ,ja: 'APIシークレットは正しくありません' ,dk: 'Forkert API-nøgle' ,fi: 'Väärä API-avain' ,nb: 'Ugyldig API nøkkel' ,pl: 'Błędny klucz API' ,ru: 'Плохой пароль API' - ,he: ' הסיסמא הסודית אינה חוקית' ,sk: 'Nesprávne API heslo' ,nl: 'Onjuist API wachtwoord' ,ko: '잘못된 API secret' + ,tr: 'Hatalı API parolası' ,zh_cn: 'API密钥错误' ,zh_tw: 'API密鑰錯誤' } @@ -2434,15 +2757,16 @@ function init() { ,hr: 'API tajni hash je pohranjen' ,sv: 'Lagrad hemlig API-hash' ,it: 'Hash API secreto memorizzato' + ,ja: 'APIシークレットを保存出来ました' ,dk: 'Hemmelig API-hash gemt' ,fi: 'API salaisuus talletettu' ,nb: 'API nøkkel lagret' ,pl: 'Poufne klucz API zapisane' - ,he: ' הסיסמא הסודית נשמרה' - ,ru: 'Пароль API сохранен' + ,ru: 'Хэш пароля API сохранен' ,sk: 'Hash API hesla uložený' ,nl: 'API wachtwoord opgeslagen' ,ko: 'API secret hash가 저장 되었습니다.' + ,tr: 'API secret hash parolası saklandı' ,zh_cn: 'API密钥已存储' ,zh_tw: 'API密鑰已存儲' } @@ -2458,6 +2782,7 @@ function init() { ,bg: 'Статус' ,hr: 'Status' ,it: 'Stato' + ,ja: '統計' ,dk: 'Status' ,fi: 'Tila' ,nb: 'Status' @@ -2467,6 +2792,7 @@ function init() { ,sk: 'Status' ,nl: 'Status' ,ko: '상태' + ,tr: 'Durum' ,zh_cn: '状态' } ,'Not loaded' : { @@ -2482,15 +2808,16 @@ function init() { ,hr: 'Nije učitano' ,sv: 'Ej laddad' ,it: 'Non caricato' + ,ja: '読み込めません' ,dk: 'Ikke indlæst' ,fi: 'Ei ladattu' ,nb: 'Ikke lest' - ,he: 'לא נטען' ,pl: 'Nie załadowany' ,ru: 'Не загружено' ,sk: 'Nenačítaný' ,nl: 'Niet geladen' ,ko: '로드되지 않음' + ,tr: 'Yüklü değil' ,zh_cn: '未载入' } ,'Food Editor' : { @@ -2506,15 +2833,16 @@ function init() { ,hr: 'Editor hrane' ,sv: 'Födoämneseditor' ,it: 'NS - Database Alimenti' - ,he: 'עורך מזון' + ,ja: '食事編集' ,dk: 'Mad editor' ,fi: 'Muokkaa ruokia' ,nb: 'Mat editor' ,pl: 'Edytor posiłków' - ,ru: 'Редактор еды' + ,ru: 'Редактор епродуктов' ,sk: 'Editor jedál' ,nl: 'Voeding beheer' - ,ko: '음식 편집기' + ,ko: '음식 편집' + ,tr: 'Gıda Editörü' ,zh_cn: '食物编辑器' } ,'Your database' : { @@ -2529,6 +2857,7 @@ function init() { ,bg: 'Твоята база с данни' ,hr: 'Vaša baza podataka' ,it: 'Vostro database' + ,ja: 'あなたのデータベース' ,dk: 'Din database' ,fi: 'Tietokantasi' ,nb: 'Din database' @@ -2538,6 +2867,7 @@ function init() { ,sk: 'Vaša databáza' ,nl: 'Uw database' ,ko: '당신의 데이터베이스' + ,tr: 'Sizin Veritabanınız' ,zh_cn: '你的数据库' } ,'Filter' : { @@ -2552,6 +2882,7 @@ function init() { ,bg: 'Филтър' ,hr: 'Filter' ,it: 'Filtro' + ,ja: 'フィルター' ,dk: 'Filter' ,fi: 'Suodatin' ,nb: 'Filter' @@ -2561,6 +2892,7 @@ function init() { ,ru: 'Фильтр' ,sk: 'Filter' ,ko: '필터' + ,tr: 'Filtre' ,zh_cn: '过滤器' } ,'Save' : { @@ -2575,6 +2907,7 @@ function init() { ,hr: 'Spremi' ,sv: 'Spara' ,it: 'Salva' + ,ja: '保存' ,dk: 'Gem' ,fi: 'Tallenna' ,nb: 'Lagre' @@ -2584,6 +2917,7 @@ function init() { ,sk: 'Uložiť' ,nl: 'Opslaan' ,ko: '저장' + ,tr: 'Kaydet' ,zh_cn: '保存' ,zh_tw: '保存' } @@ -2599,6 +2933,7 @@ function init() { ,hr: 'Očisti' ,sv: 'Rensa' ,it: 'Pulisci' + ,ja: 'クリア' ,dk: 'Rense' ,fi: 'Tyhjennä' ,nb: 'Tøm' @@ -2608,6 +2943,7 @@ function init() { ,sk: 'Vymazať' ,nl: 'Leeg maken' ,ko: '취소' + ,tr: 'Temizle' ,zh_cn: '清除' } ,'Record' : { @@ -2622,6 +2958,7 @@ function init() { ,bg: 'Запиши' ,hr: 'Zapis' ,it: 'Registro' + ,ja: '記録' ,dk: 'Post' ,fi: 'Tietue' ,nb: 'Registrering' @@ -2631,6 +2968,7 @@ function init() { ,sk: 'Záznam' ,nl: 'Toevoegen' ,ko: '기록' + ,tr: 'Kayıt' ,zh_cn: '记录' } ,'Quick picks' : { @@ -2645,6 +2983,7 @@ function init() { ,hr: 'Brzi izbor' ,sv: 'Snabbval' ,it: 'Scelta rapida' + ,ja: 'クイック選択' ,dk: 'Hurtig valg' ,fi: 'Nopeat valinnat' ,nb: 'Hurtigvalg' @@ -2654,6 +2993,7 @@ function init() { ,sk: 'Rýchly výber' ,nl: 'Snelkeuze' ,ko: '빠른 선택' + ,tr: 'Hızlı seçim' ,zh_cn: '快速选择' } ,'Show hidden' : { @@ -2668,6 +3008,7 @@ function init() { ,hr: 'Prikaži skriveno' ,sv: 'Visa dolda' ,it: 'Mostra nascosto' + ,ja: '表示する' ,dk: 'Vis skjulte' ,fi: 'Näytä piilotettu' ,nb: 'Vis skjulte' @@ -2677,57 +3018,14 @@ function init() { ,sk: 'Zobraziť skryté' ,nl: 'Laat verborgen zien' ,ko: '숨김 보기' + ,tr: 'Gizli göster' ,zh_cn: '显示隐藏值' } - ,'Your API secret' : { - cs: 'Vaše API heslo' - ,he: 'הסיסמא הסודית שלך' - ,de: 'Deine API-Prüfsumme' - ,es: 'Su API secreto' - ,fr: 'Votre secret API' - ,el: 'Το συνθηματικό σας' - ,pt: 'Seu segredo de API' - ,sv: 'Din API-nyckel' - ,ro: 'Cheia API' - ,bg: 'Твоята API парола' - ,hr: 'Vaš tajni API' - ,he: 'הסיסמא הסודית שלך' - ,it: 'Il tuo API secreto' - ,dk: 'Din API-nøgle' - ,fi: 'Sinun API-avaimesi' - ,nb: 'Din API nøkkel' - ,pl: 'Twoje poufne hasło API' - ,ru: 'Ваш пароль API' - ,sk: 'Vaše API heslo' - ,nl: 'Uw API wachtwoord' - ,ko: 'API secret' - ,zh_cn: 'API密钥' - ,zh_tw: 'API密鑰' - } - ,'Store hash on this computer (Use only on private computers)' : { - cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' - ,he: 'אחסן את הסיסמא הסודית שלך על מחשב זה.מומלץ לעשות כן רק אם המחשב בשימושך הפרטי' - ,de: 'Speichere Prüfsumme auf diesem Computer (nur auf privaten Computern verwenden)' - ,es: 'Guardar hash en este ordenador (Usar solo en ordenadores privados)' - ,fr: 'Sauvegarder le hash sur cet ordinateur (privé uniquement)' - ,el: 'Αποθήκευση συνθηματικού σε αυτό τον υπολογιστή (μόνο για υπολογιστές προσωπικής χρήσης)' - ,pt: 'Salvar hash nesse computador (Somente em computadores privados)' - ,ro: 'Salvează cheia pe acest PC (Folosiți doar PC de încredere)' - ,bg: 'Запамети данните на този компютър. ( Използвай само на собствен компютър)' - ,hr: 'Pohrani hash na ovom računalu (Koristiti samo na osobnom računalu)' - ,sv: 'Lagra hashvärde på denna dator (använd endast på privat dator)' - ,it: 'Conservare hash su questo computer (utilizzare solo su computer privati)' - ,dk: 'Gemme hash på denne computer (brug kun på privat computer)' - ,fi: 'Tallenna avain tälle tietokoneelle (käytä vain omalla tietokoneellasi)' - ,nb: 'Lagre hash på denne pc (bruk kun på privat pc)' - ,he:'שמור סיסמא הסודית על המחשב ( יש להשתמש רק על מחשב פרטי)' - ,pl: 'Zapisz na tym komputerze (korzystaj tylko na komputerach prywatnych)' - ,ru: 'Сохранить на этом ПК (только для личных компьютеров)' - ,sk: 'Uložiť hash na tomto počítači (Používajte iba na súkromných počítačoch)' - ,nl: 'Sla wachtwoord op. (gebruik dit alleen op prive computers)' - ,ko: '이 컴퓨터에 hash를 저장하세요.(단, 개인 컴퓨터를 사용하세요.)' - ,zh_cn: '在本机存储API密钥\n(请勿在公用电脑上使用本功能)' - ,zh_tw: '在本機存儲API密鑰\n(請勿在公用電腦上使用本功能)' + ,'Your API secret or token' : { + fi: 'API salaisuus tai avain' + } + ,'Remember this device. (Do not enable this on public computers.)' : { + fi: 'Muista tämä laite (Älä valitse julkisilla tietokoneilla)' } ,'Treatments' : { cs: 'Ošetření' @@ -2741,6 +3039,7 @@ function init() { ,bg: 'Събития' ,hr: 'Tretmani' ,it: 'Somministrazioni' + ,ja: '治療' ,dk: 'Behandling' ,fi: 'Hoitotoimenpiteet' ,nb: 'Behandlinger' @@ -2749,7 +3048,8 @@ function init() { ,ru: 'Лечение' ,sk: 'Ošetrenie' ,nl: 'Behandelingen' - ,ko: '대처' + ,ko: '관리' + ,tr: 'Tedaviler' ,zh_cn: '操作' } ,'Time' : { @@ -2764,6 +3064,7 @@ function init() { ,bg: 'Време' ,hr: 'Vrijeme' ,it: 'Tempo' + ,ja: '時間' ,dk: 'Tid' ,fi: 'Aika' ,nb: 'Tid' @@ -2773,6 +3074,7 @@ function init() { ,sk: 'Čas' ,nl: 'Tijd' ,ko: '시간' + ,tr: 'Zaman' ,zh_cn: '时间' ,zh_tw: '時間' } @@ -2788,6 +3090,7 @@ function init() { ,bg: 'Вид събитие' ,hr: 'Vrsta događaja' ,it: 'Tipo di evento' + ,ja: 'イベント' ,dk: 'Hændelsestype' ,fi: 'Tapahtumatyyppi' ,nb: 'Type' @@ -2797,6 +3100,7 @@ function init() { ,sk: 'Typ udalosti' ,nl: 'Soort' ,ko: '입력 유형' + ,tr: 'Etkinlik tipi' ,zh_cn: '事件类型' } ,'Blood Glucose' : { @@ -2811,15 +3115,17 @@ function init() { ,bg: 'Кръвна захар' ,hr: 'GUK' ,it: 'Glicemie' + ,ja: '血糖値' ,dk: 'Glukoseværdi' ,fi: 'Verensokeri' ,nb: 'Blodsukker' ,he: 'סוכר בדם' ,pl: 'Glikemia z krwi' - ,ru: 'Сахар крови' + ,ru: 'Гликемия' ,sk: 'Glykémia' ,nl: 'Bloed glucose' ,ko: '혈당' + ,tr: 'Kan Şekeri' ,zh_cn: '血糖值' } ,'Entered By' : { @@ -2834,6 +3140,7 @@ function init() { ,bg: 'Въведено от' ,hr: 'Unos izvršio' ,it: 'inserito da' + ,ja: '入力者' ,dk: 'Indtastet af' ,fi: 'Tiedot syötti' ,nb: 'Lagt inn av' @@ -2842,7 +3149,8 @@ function init() { ,ru: 'Введено от' ,sk: 'Zadal' ,nl: 'Ingevoerd door' - ,ko: '입력됨' + ,ko: '입력 내용' + ,tr: 'Tarafından girildi' ,zh_cn: '输入人' } ,'Delete this treatment?' : { @@ -2857,15 +3165,17 @@ function init() { ,hr: 'Izbriši ovaj tretman?' ,sv: 'Ta bort händelse?' ,it: 'Eliminare questa somministrazione?' + ,ja: 'この治療データを削除しますか?' ,dk: 'Slet denne hændelse?' ,fi: 'Tuhoa tämä hoitotoimenpide?' ,nb: 'Slett denne hendelsen?' ,he: 'למחוק רשומה זו?' ,pl: 'Usunąć te leczenie?' - ,ru: 'Удалить эту запись лечения' + ,ru: 'Удалить это событие?' ,sk: 'Vymazať toto ošetrenie?' ,nl: 'Verwijder' ,ko: '이 대처를 지울까요?' + ,tr: 'Bu tedaviyi sil?' ,zh_cn: '删除这个操作?' } ,'Carbs Given' : { @@ -2877,9 +3187,10 @@ function init() { ,pt: 'Carboidratos' ,ro: 'Carbohidrați' ,bg: 'ВХ' - ,hr: 'Količina UH' + ,hr: 'Količina UGH' ,sv: 'Antal kolhydrater' ,it: 'Carboidrati' + ,ja: '摂取糖質量' ,dk: 'Antal kulhydrater' ,fi: 'Hiilihydraatit' ,nb: 'Karbo' @@ -2889,6 +3200,7 @@ function init() { ,sk: 'Sacharidov' ,nl: 'Aantal koolhydraten' ,ko: '탄수화물 요구량' + ,tr: 'Karbonhidrat Verilen' ,zh_cn: '碳水化合物量' } ,'Inzulin Given' : { @@ -2903,6 +3215,7 @@ function init() { ,hr: 'Količina inzulina' ,sv: 'Insulin' ,it: 'Insulina' + ,ja: 'インスリン投与量' ,dk: 'Insulin' ,fi: 'Insuliiniannos' ,nb: 'Insulin' @@ -2912,6 +3225,7 @@ function init() { ,sk: 'Inzulínu' ,nl: 'Insuline' ,ko: '인슐린 요구량' + ,tr: 'İnsülin Verilen' ,zh_cn: '胰岛素输注' } ,'Event Time' : { @@ -2926,6 +3240,7 @@ function init() { ,bg: 'Въвеждане' ,hr: 'Vrijeme događaja' ,it: 'Ora Evento' + ,ja: 'イベント時間' ,dk: 'Tidspunkt for hændelsen' ,fi: 'Aika' ,nb: 'Tidspunkt for hendelsen' @@ -2935,6 +3250,7 @@ function init() { ,sk: 'Čas udalosti' ,nl: 'Tijdstip' ,ko: '입력 시간' + ,tr: 'Etkinliğin zamanı' ,zh_cn: '事件时间' } ,'Please verify that the data entered is correct' : { @@ -2949,6 +3265,7 @@ function init() { ,hr: 'Molim Vas provjerite jesu li uneseni podaci ispravni' ,sv: 'Vänligen verifiera att inlagd data är korrekt' ,it: 'Si prega di verificare che i dati inseriti siano corretti' + ,ja: '入力したデータが正しいか確認をお願いします。' ,dk: 'Venligst verificer at indtastet data er korrekt' ,fi: 'Varmista, että tiedot ovat oikein' ,nb: 'Vennligst verifiser at inntastet data er korrekt' @@ -2958,6 +3275,7 @@ function init() { ,sk: 'Prosím, skontrolujte správnosť zadaných údajov' ,nl: 'Controleer uw invoer' ,ko: '입력한 데이터가 정확한지 확인해 주세요.' + ,tr: 'Lütfen girilen verilerin doğru olduğunu kontrol edin.' ,zh_cn: '请验证输入的数据是否正确' } ,'BG' : { @@ -2973,14 +3291,16 @@ function init() { ,bg: 'КЗ' ,hr: 'GUK' ,it: 'Glicemie' + ,ja: 'BG' ,dk: 'BS' ,fi: 'VS' ,nb: 'BS' ,pl: 'BG' - ,ru: 'Гликемия' + ,ru: 'ГК' ,sk: 'Glykémia' ,nl: 'BG' ,ko: '혈당' + ,tr: 'KŞ' ,zh_cn: '血糖' } ,'Use BG correction in calculation' : { @@ -2996,14 +3316,16 @@ function init() { ,hr: 'Koristi korekciju GUK-a u izračunu' ,sv: 'Använd BS-korrektion för beräkning' ,it: 'Utilizzare la correzione nei calcoli delle Glicemie' + ,ja: 'ボーラス計算機能使用' ,dk: 'Anvend BS-korrektion i beregning' ,fi: 'Käytä korjausannosta laskentaan' ,nb: 'Bruk blodsukkerkorrigering i beregning' ,pl: 'Użyj BG w obliczeniach korekty' - ,ru: 'При рассчете проводите коррекцию на СК' + ,ru: 'При расчете учитывать коррекцию ГК' ,sk: 'Použite korekciu na glykémiu' ,nl: 'Gebruik BG in berekeningen' ,ko: '계산에 보정된 혈당을 사용하세요.' + ,tr: 'Hesaplamada KŞ düzeltmesini kullan' ,zh_cn: '使用血糖值修正计算' } ,'BG from CGM (autoupdated)' : { @@ -3019,14 +3341,16 @@ function init() { ,bg: 'КЗ от сензора (автоматично)' ,hr: 'GUK sa CGM-a (ažuriran automatski)' ,it: 'Glicemie da CGM (aggiornamento automatico)' + ,ja: 'CGMグルコース値' ,dk: 'BS fra CGM (automatisk)' ,fi: 'VS sensorilta (päivitetty automaattisesti)' ,nb: 'BS fra CGM (automatisk)' ,pl: 'Wartość BG z CGM (automatycznie)' - ,ru: 'СК с сенсора (автообновление)' + ,ru: 'ГК с сенсора (автообновление)' ,sk: 'Glykémia z CGM (automatická aktualizácia) ' ,nl: 'BG van CGM (automatische invoer)' ,ko: 'CGM 혈당(자동 업데이트)' + ,tr: 'CGM den KŞ (otomatik güncelleme)' ,zh_cn: 'CGM(连续血糖监测)测量的血糖值(自动更新)' } ,'BG from meter' : { @@ -3042,14 +3366,16 @@ function init() { ,bg: 'КЗ от глюкомер' ,hr: 'GUK s glukometra' ,it: 'Glicemie da glucometro' + ,ja: '血糖測定器使用グルコース値' ,dk: 'BS fra blodsukkerapperat' ,fi: 'VS mittarilta' ,nb: 'BS fra blodsukkerapparat' ,pl: 'Wartość BG z glukometru' - ,ru: 'СК по глюкометру' + ,ru: 'ГК по глюкометру' ,sk: 'Glykémia z glukomeru' ,nl: 'BG van meter' ,ko: '혈당 측정기에서의 혈당' + ,tr: 'Glikometre KŞ' ,zh_cn: '血糖仪测量的血糖值' } ,'Manual BG' : { @@ -3065,14 +3391,16 @@ function init() { ,hr: 'Ručno unesen GUK' ,sv: 'Manuellt BS' ,it: 'Inserisci Glicemia' + ,ja: '手動入力グルコース値' ,dk: 'Manuelt BS' ,fi: 'Käsin syötetty VS' ,nb: 'Manuelt BS' ,pl: 'Ręczne wprowadzenie BG' - ,ru: 'ввести данные СК вручную' + ,ru: 'ручной ввод ГК' ,sk: 'Ručne zadaná glykémia' ,nl: 'Handmatige BG' ,ko: '수동 입력 혈당' + ,tr: 'Manuel KŞ' ,zh_cn: '手动输入的血糖值' } ,'Quickpick' : { @@ -3088,6 +3416,7 @@ function init() { ,hr: 'Brzi izbor' ,sv: 'Snabbval' ,it: 'Scelta rapida' + ,ja: 'クイック選択' ,dk: 'Hurtig valg' ,fi: 'Pikavalinta' ,nb: 'Hurtigvalg' @@ -3096,6 +3425,7 @@ function init() { ,sk: 'Rýchly výber' ,nl: 'Snelkeuze' ,ko: '빠른 선택' + ,tr: 'Hızlı seçim' ,zh_cn: '快速选择' } ,'or' : { @@ -3110,6 +3440,7 @@ function init() { ,bg: 'или' ,hr: 'ili' ,it: 'o' + ,ja: 'または' ,dk: 'eller' ,fi: 'tai' ,nb: 'eller' @@ -3119,6 +3450,7 @@ function init() { ,sk: 'alebo' ,nl: 'of' ,ko: '또는' + ,tr: 'veya' ,zh_cn: '或' } ,'Add from database' : { @@ -3133,6 +3465,7 @@ function init() { ,hr: 'Dodaj iz baze podataka' ,sv: 'Lägg till från databas' ,it: 'Aggiungi dal database' + ,ja: 'データベースから追加' ,dk: 'Tilføj fra database' ,fi: 'Lisää tietokannasta' ,nb: 'Legg til fra database' @@ -3142,6 +3475,7 @@ function init() { ,sk: 'Pridať z databázy' ,nl: 'Toevoegen uit database' ,ko: '데이터베이스로 부터 추가' + ,tr: 'Veritabanından ekle' ,zh_cn: '从数据库增加' } ,'Use carbs correction in calculation' : { @@ -3157,14 +3491,16 @@ function init() { ,hr: 'Koristi korekciju za UH u izračunu' ,sv: 'Använd kolhydratkorrektion för beräkning' ,it: 'Utilizzare la correzione dei carboidrati nel calcolo' + ,ja: '糖質量計算機能を使用' ,dk: 'Benyt kulhydratkorrektion i beregning' ,fi: 'Käytä hiilihydraattikorjausta laskennassa' ,nb: 'Bruk karbohydratkorrigering i beregning' ,pl: 'Użyj wartość węglowodanów w obliczeniach korekty' - ,ru: 'Пользуйтесь коррекцией углеводов при рассчете' + ,ru: 'Пользуйтесь коррекцией на углеводы при расчете' ,sk: 'Použite korekciu na sacharidy' ,nl: 'Gebruik KH correctie in berekening' ,ko: '계산에 보정된 탄수화물을 사용하세요.' + ,tr: 'Hesaplamada karbonhidrat düzeltmesini kullan' ,zh_cn: '使用碳水化合物修正计算结果' } ,'Use COB correction in calculation' : { @@ -3177,17 +3513,19 @@ function init() { ,pt: 'Usar correção de COB no cálculo' ,ro: 'Folosește COB în calcule' ,bg: 'Включи активните ВХ в изчислението' - ,hr: 'Koristi aktivne UH u izračunu' + ,hr: 'Koristi aktivne UGH u izračunu' ,sv: 'Använd aktiva kolhydrater för beräkning' ,it: 'Utilizzare la correzione COB nel calcolo' + ,ja: 'COB補正計算を使用' ,dk: 'Benyt aktive kulhydrater i beregning' ,fi: 'Käytä aktiivisia hiilihydraatteja laskennassa' ,nb: 'Benytt aktive karbohydrater i beregning' ,pl: 'Użyj COB do obliczenia korekty' - ,ru: 'Учитывайте активные углеводы при рассчете (COB)' + ,ru: 'Учитывайте активные углеводы COB при расчете' ,sk: 'Použite korekciu na COB' ,nl: 'Gebruik ingenomen KH in berekening' ,ko: '계산에 보정된 COB를 사용하세요.' + ,tr: 'Hesaplamada COB aktif karbonhidrat düzeltmesini kullan' ,zh_cn: '使用COB(活性碳水化合物)修正计算结果' } ,'Use IOB in calculation' : { @@ -3203,14 +3541,16 @@ function init() { ,hr: 'Koristi aktivni inzulin u izračunu' ,sv: 'Använd aktivt insulin för beräkning' ,it: 'Utilizzare la correzione IOB nel calcolo' + ,ja: 'IOB計算を使用' ,dk: 'Benyt aktivt insulin i beregningen' ,fi: 'Käytä aktiviivista insuliinia laskennassa' ,nb: 'Bruk aktivt insulin i beregningen' ,pl: 'Użyj IOB w obliczeniach' - ,ru: 'Учитывайте активный инсулин при рассчете (IOB)' + ,ru: 'Учитывайте активный инсулин IOB при расчете' ,sk: 'Použite IOB vo výpočte' ,nl: 'Gebruik IOB in berekening' ,ko: '계산에 IOB를 사용하세요.' + ,tr: 'Hesaplamada IOB aktif insülin düzeltmesini kullan' ,zh_cn: '使用IOB(活性胰岛素)修正计算结果' } ,'Other correction' : { @@ -3226,6 +3566,7 @@ function init() { ,hr: 'Druga korekcija' ,sv: 'Övrig korrektion' ,it: 'Altre correzioni' + ,ja: 'その他の補正' ,dk: 'Øvrig korrektion' ,fi: 'Muu korjaus' ,nb: 'Annen korrigering' @@ -3234,6 +3575,7 @@ function init() { ,sk: 'Iná korekcia' ,nl: 'Andere correctie' ,ko: '다른 보정' + ,tr: 'Diğer düzeltme' ,zh_cn: '其它修正' } ,'Rounding' : { @@ -3249,6 +3591,7 @@ function init() { ,bg: 'Закръгляне' ,hr: 'Zaokruživanje' ,it: 'Arrotondamento' + ,ja: '端数処理' ,dk: 'Afrunding' ,fi: 'Pyöristys' ,nb: 'Avrunding' @@ -3257,6 +3600,7 @@ function init() { ,sk: 'Zaokrúhlenie' ,nl: 'Afgerond' ,ko: '라운딩' + ,tr: 'yuvarlama' ,zh_cn: '取整' } ,'Enter insulin correction in treatment' : { @@ -3272,6 +3616,7 @@ function init() { ,hr: 'Unesi korekciju inzulinom u tretman' ,sv: 'Ange insulinkorrektion för händelse' ,it: 'Inserisci correzione insulina nella somministrazione' + ,ja: '治療にインスリン補正を入力する。' ,dk: 'Indtast insulinkorrektion' ,fi: 'Syötä insuliinikorjaus' ,nb: 'Task inn insulinkorrigering' @@ -3280,6 +3625,7 @@ function init() { ,sk: 'Zadajte korekciu inzulínu do ošetrenia' ,nl: 'Voer insuline correctie toe aan behandeling' ,ko: '대처를 위해 보정된 인슐린을 입력하세요.' + ,tr: 'Tedavide insülin düzeltmesini girin' ,zh_cn: '在操作中输入胰岛素修正' } ,'Insulin needed' : { @@ -3294,6 +3640,7 @@ function init() { ,hr: 'Potrebno inzulina' ,sv: 'Beräknad insulinmängd' ,it: 'Insulina necessaria' + ,ja: '必要インスリン単位' ,dk: 'Insulin påkrævet' ,fi: 'Insuliinitarve' ,nb: 'Insulin nødvendig' @@ -3303,6 +3650,7 @@ function init() { ,sk: 'Potrebný inzulín' ,nl: 'Benodigde insuline' ,ko: '인슐린 필요' + ,tr: 'İnsülin gerekli' ,zh_cn: '需要的胰岛素量' } ,'Carbs needed' : { @@ -3314,9 +3662,10 @@ function init() { ,pt: 'Carboidratos necessários' ,ro: 'Necesar carbohidrați' ,bg: 'Необходими въглехидрати' - ,hr: 'Potrebno UH' + ,hr: 'Potrebno UGH' ,sv: 'Beräknad kolhydratmängd' ,it: 'Carboidrati necessari' + ,ja: '必要糖質量' ,dk: 'Kulhydrater påkrævet' ,fi: 'Hiilihydraattitarve' ,nb: 'Karbohydrater nødvendig' @@ -3326,6 +3675,7 @@ function init() { ,sk: 'Potrebné sacharidy' ,nl: 'Benodigde koolhydraten' ,ko: '탄수화물 필요' + ,tr: 'Karbonhidrat gerekli' ,zh_cn: '需要的碳水量' } ,'Carbs needed if Insulin total is negative value' : { @@ -3338,9 +3688,10 @@ function init() { ,pt: 'Carboidratos necessários se Insulina total for negativa' ,ro: 'Carbohidrați când necesarul de insulină este negativ' ,bg: 'Необходими въглехидрати, ако няма инсулин' - ,hr: 'Potrebno UH ako je ukupna vrijednost inzulina negativna' + ,hr: 'Potrebno UGH ako je ukupna vrijednost inzulina negativna' ,sv: 'Nödvändig kolhydratmängd för angiven insulinmängd' ,it: 'Carboidrati necessari se l\'insulina totale è un valore negativo' + ,ja: 'インスリン合計値がマイナスであればカーボ値入力が必要です。' ,dk: 'Kulhydrater er nødvendige når total insulin mængde er negativ' ,fi: 'Hiilihydraattitarve, jos yhteenlaskettu insuliini on negatiivinen' ,nb: 'Karbohydrater er nødvendige når total insulinmengde er negativ' @@ -3349,6 +3700,7 @@ function init() { ,sk: 'Potrebné sacharidy, ak je celkový inzulín záporná hodnota' ,nl: 'Benodigde KH als insuline een negatieve waarde geeft' ,ko: '인슐린 전체가 마이너스 값이면 탄수화물이 필요합니다.' + ,tr: 'Toplam insülin negatif değer olduğunda karbonhidrat gereklidir' ,zh_cn: '如果胰岛素总量为负时所需的碳水化合物量' } ,'Basal rate' : { @@ -3364,14 +3716,16 @@ function init() { ,hr: 'Bazal' ,sv: 'Basaldos' ,it: 'Velocità basale' + ,ja: '基礎インスリン割合' ,dk: 'Basal rate' ,fi: 'Perusannos' ,nb: 'Basal' ,pl: 'Dawka bazowa' - ,ru: 'Базальный' + ,ru: 'Скорость базала' ,sk: 'Bazál' ,nl: 'Basaal snelheid' - ,ko: '기초량 비율' + ,ko: 'Basal 단위' + ,tr: 'Basal oranı' ,zh_cn: '基础率' } ,'60 minutes earlier' : { @@ -3387,14 +3741,16 @@ function init() { ,bg: 'Преди 60 минути' ,hr: 'Prije 60 minuta' ,it: '60 minuti prima' + ,ja: '60分前' ,dk: '60 min tidligere' ,fi: '60 minuuttia aiemmin' ,nb: '60 min tidligere' ,pl: '60 minut wcześniej' - ,ru: 'на 60 минут раньше' + ,ru: 'на 60 минут ранее' ,sk: '60 min. pred' ,nl: '60 minuten eerder' ,ko: '60분 더 일찍' + ,tr: '60 dak. önce' //erken önce ??? ,zh_cn: '60分钟前' } ,'45 minutes earlier' : { @@ -3410,14 +3766,16 @@ function init() { ,bg: 'Преди 45 минути' ,hr: 'Prije 45 minuta' ,it: '45 minuti prima' + ,ja: '45分前' ,dk: '45 min tidligere' ,fi: '45 minuuttia aiemmin' ,nb: '45 min tidligere' ,pl: '45 minut wcześniej' - ,ru: 'на 45 минут раньше' + ,ru: 'на 45 минут ранее' ,sk: '45 min. pred' ,nl: '45 minuten eerder' ,ko: '45분 더 일찍' + ,tr: '45 dak. önce' ,zh_cn: '45分钟前' } ,'30 minutes earlier' : { @@ -3433,14 +3791,16 @@ function init() { ,bg: 'Преди 30 минути' ,hr: 'Prije 30 minuta' ,it: '30 minuti prima' + ,ja: '30分前' ,dk: '30 min tidigere' ,fi: '30 minuuttia aiemmin' ,nb: '30 min tidigere' ,pl: '30 minut wcześniej' - ,ru: 'на 30 минут раньше' + ,ru: 'на 30 минут ранее' ,sk: '30 min. pred' ,nl: '30 minuten eerder' ,ko: '30분 더 일찍' + ,tr: '30 dak. önce' ,zh_cn: '30分钟前' } ,'20 minutes earlier' : { @@ -3456,14 +3816,16 @@ function init() { ,bg: 'Преди 20 минути' ,hr: 'Prije 20 minuta' ,it: '20 minuti prima' + ,ja: '20分前' ,dk: '20 min tidligere' ,fi: '20 minuuttia aiemmin' ,nb: '20 min tidligere' ,pl: '20 minut wcześniej' - ,ru: 'на 20 минут раньше' + ,ru: 'на 20 минут ранее' ,sk: '20 min. pred' ,nl: '20 minuten eerder' ,ko: '20분 더 일찍' + ,tr: '20 dak. önce' ,zh_cn: '20分钟前' } ,'15 minutes earlier' : { @@ -3479,14 +3841,16 @@ function init() { ,bg: 'Преди 15 минути' ,hr: 'Prije 15 minuta' ,it: '15 minuti prima' + ,ja: '15分前' ,dk: '15 min tidligere' ,fi: '15 minuuttia aiemmin' ,nb: '15 min tidligere' ,pl: '15 minut wcześniej' - ,ru: 'на 15 минут раньше' + ,ru: 'на 15 минут ранее' ,sk: '15 min. pred' ,nl: '15 minuten eerder' ,ko: '15분 더 일찍' + ,tr: '15 dak. önce' ,zh_cn: '15分钟前' } ,'Time in minutes' : { @@ -3510,6 +3874,7 @@ function init() { ,sk: 'Čas v minútach' ,nl: 'Tijd in minuten' ,ko: '분' + ,tr: 'Dakika cinsinden süre' ,zh_cn: '1分钟前' } ,'15 minutes later' : { @@ -3524,6 +3889,7 @@ function init() { ,hr: '15 minuta kasnije' ,sv: '15 min senare' ,it: '15 minuti più tardi' + ,ja: '15分後' ,dk: '15 min senere' ,fi: '15 minuuttia myöhemmin' ,nb: '15 min senere' @@ -3533,6 +3899,7 @@ function init() { ,sk: '15 min. po' ,nl: '15 minuten later' ,ko: '15분 더 나중에' + ,tr: '15 dak. sonra' //sonra daha sonra ,zh_cn: '15分钟后' } ,'20 minutes later' : { @@ -3547,6 +3914,7 @@ function init() { ,hr: '20 minuta kasnije' ,sv: '20 min senare' ,it: '20 minuti più tardi' + ,ja: '20分後' ,dk: '20 min senere' ,fi: '20 minuuttia myöhemmin' ,nb: '20 min senere' @@ -3556,6 +3924,7 @@ function init() { ,sk: '20 min. po' ,nl: '20 minuten later' ,ko: '20분 더 나중에' + ,tr: '20 dak. sonra' ,zh_cn: '20分钟后' } ,'30 minutes later' : { @@ -3570,6 +3939,7 @@ function init() { ,hr: '30 minuta kasnije' ,sv: '30 min senare' ,it: '30 minuti più tardi' + ,ja: '30分後' ,dk: '30 min senere' ,fi: '30 minuuttia myöhemmin' ,nb: '30 min senere' @@ -3579,6 +3949,7 @@ function init() { ,sk: '30 min. po' ,nl: '30 minuten later' ,ko: '30분 더 나중에' + ,tr: '30 dak. sonra' ,zh_cn: '30分钟后' } ,'45 minutes later' : { @@ -3593,6 +3964,7 @@ function init() { ,hr: '45 minuta kasnije' ,sv: '45 min senare' ,it: '45 minuti più tardi' + ,ja: '45分後' ,dk: '45 min senere' ,fi: '45 minuuttia myöhemmin' ,nb: '45 min senere' @@ -3602,6 +3974,7 @@ function init() { ,sk: '45 min. po' ,nl: '45 minuten later' ,ko: '45분 더 나중에' + ,tr: '45 dak. sonra' ,zh_cn: '45分钟后' } ,'60 minutes later' : { @@ -3616,6 +3989,7 @@ function init() { ,hr: '60 minuta kasnije' ,sv: '60 min senare' ,it: '60 minuti più tardi' + ,ja: '60分後' ,dk: '60 min senere' ,fi: '60 minuuttia myöhemmin' ,nb: '60 min senere' @@ -3625,6 +3999,7 @@ function init() { ,sk: '60 min. po' ,nl: '60 minuten later' ,ko: '60분 더 나중에' + ,tr: '60 dak. sonra' ,zh_cn: '60分钟后' } ,'Additional Notes, Comments' : { @@ -3639,6 +4014,7 @@ function init() { ,hr: 'Dodatne bilješke, komentari' ,sv: 'Notering, övrigt' ,it: 'Note aggiuntive, commenti' + ,ja: '追加メモ、コメント' ,dk: 'Ekstra noter, kommentarer' ,fi: 'Lisähuomiot, kommentit' ,nb: 'Ekstra notater, kommentarer' @@ -3648,6 +4024,7 @@ function init() { ,sk: 'Ďalšie poznámky, komentáre' ,nl: 'Extra aantekeningen' ,ko: '추가 메모' + ,tr: 'Ek Notlar, Yorumlar' ,zh_cn: '备注' } ,'RETRO MODE' : { @@ -3663,6 +4040,7 @@ function init() { ,bg: 'МИНАЛО ВРЕМЕ' ,hr: 'Retrospektivni način' ,it: 'Modalità retrospettiva' + ,ja: '振り返りモード' ,dk: 'Retro mode' ,fi: 'VANHENTUNEET TIEDOT' ,nb: 'Retro mode' @@ -3671,6 +4049,7 @@ function init() { ,ru: 'режим РЕТРО' ,sk: 'V MINULOSTI' ,ko: 'PETRO MODE' + ,tr: 'RETRO MODE' ,zh_cn: '历史模式' } ,'Now' : { @@ -3685,6 +4064,7 @@ function init() { ,bg: 'Сега' ,hr: 'Sad' ,it: 'Ora' + ,ja: '今' ,dk: 'Nu' ,fi: 'Nyt' ,nb: 'Nå' @@ -3694,6 +4074,7 @@ function init() { ,sk: 'Teraz' ,nl: 'Nu' ,ko: '현재' + ,tr: 'Şimdi' ,zh_cn: '现在' } ,'Other' : { @@ -3708,6 +4089,7 @@ function init() { ,bg: 'Друго' ,hr: 'Drugo' ,it: 'Altro' + ,ja: '他の' ,dk: 'Øvrige' ,fi: 'Muu' ,nb: 'Annet' @@ -3717,6 +4099,7 @@ function init() { ,sk: 'Iný' ,nl: 'Andere' ,ko: '다른' + ,tr: 'Diğer' ,zh_cn: '其它' } ,'Submit Form' : { @@ -3729,8 +4112,9 @@ function init() { ,sv: 'Överför händelse' ,ro: 'Trimite formularul' ,bg: 'Въвеждане на данните' - ,hr: 'Predaj obrazac' + ,hr: 'Spremi' ,it: 'Invia il modulo' + ,ja: 'フォームを投稿する' ,dk: 'Gem hændelsen' ,fi: 'Lähetä tiedot' ,nb: 'Lagre' @@ -3740,6 +4124,7 @@ function init() { ,sk: 'Odoslať formulár' ,nl: 'Formulier opslaan' ,ko: '양식 제출' + ,tr: 'Formu gönder' ,zh_cn: '提交' } ,'Profile Editor' : { @@ -3754,6 +4139,7 @@ function init() { ,bg: 'Редактор на профила' ,hr: 'Editor profila' ,it: 'NS - Dati Personali' + ,ja: 'プロフィール編集' ,dk: 'Profil editor' ,fi: 'Profiilin muokkaus' ,nb: 'Profileditor' @@ -3762,7 +4148,8 @@ function init() { ,ru: 'Редактор профиля' ,sk: 'Editor profilu' ,nl: 'Profiel beheer' - ,ko: '프로 편집기' + ,ko: '프로파일 편집' + ,tr: 'Profil Düzenleyicisi' ,zh_cn: '配置文件编辑器' ,zh_tw: '配置文件編輯器' } @@ -3777,8 +4164,9 @@ function init() { ,sv: 'Rapportverktyg' ,ro: 'Instrumente raportare' ,bg: 'Статистика' - ,hr: 'Alat za prijavu' + ,hr: 'Izvještaji' ,it: 'NS - Statistiche' + ,ja: '報告' ,dk: 'Rapporteringsværktøj' ,fi: 'Raportointityökalu' ,nb: 'Rapporteringsverktøy' @@ -3787,6 +4175,7 @@ function init() { ,sk: 'Správy' ,nl: 'Rapporten' ,ko: '보고서' + ,tr: 'Raporlar' ,zh_cn: '生成报表' ,zh_tw: '生成報表' } @@ -3803,6 +4192,7 @@ function init() { ,hr: 'Dodajte hranu iz svoje baze podataka' ,sv: 'Lägg till livsmedel från databas' ,it: 'Aggiungere cibo al database' + ,ja: 'データベースから食べ物を追加' ,dk: 'Tilføj mad fra din database' ,fi: 'Lisää ruoka tietokannasta' ,nb: 'Legg til mat fra din database' @@ -3811,6 +4201,7 @@ function init() { ,sk: 'Pridať jedlo z Vašej databázy' ,nl: 'Voeg voeding toe uit uw database' ,ko: '데이터베이스에서 음식을 추가하세요.' + ,tr: 'Veritabanınızdan yemek ekleyin' ,zh_cn: '从数据库增加食物' } ,'Reload database' : { @@ -3826,14 +4217,16 @@ function init() { ,hr: 'Ponovo učitajte bazu podataka' ,sv: 'Ladda om databas' ,it: 'Ricarica database' + ,ja: 'データベース再読み込み' ,dk: 'Genindlæs databasen' ,fi: 'Lataa tietokanta uudelleen' ,nb: 'Last inn databasen på nytt' ,pl: 'Odśwież bazę danych' - ,ru: 'Перезагрузить базу данных' + ,ru: 'Перезагрузите базу данных' ,sk: 'Obnoviť databázu' ,nl: 'Database opnieuw laden' ,ko: '데이터베이스 재로드' + ,tr: 'Veritabanını yeniden yükle' ,zh_cn: '重新载入数据库' } ,'Add' : { @@ -3849,14 +4242,16 @@ function init() { ,hr: 'Dodaj' ,sv: 'Lägg till' ,it: 'Aggiungere' + ,ja: '追加' ,dk: 'Tilføj' ,fi: 'Lisää' ,nb: 'Legg til' ,pl: 'Dodaj' - ,ru: 'Добавить' + ,ru: 'Добавьте' ,sk: 'Pridať' ,nl: 'Toevoegen' ,ko: '추가' + ,tr: 'Ekle' ,zh_cn: '增加' } ,'Unauthorized' : { @@ -3872,14 +4267,16 @@ function init() { ,bg: 'Неразрешен достъп' ,hr: 'Neautorizirano' ,it: 'Non Autorizzato' + ,ja: '認証されていません' ,dk: 'Uautoriseret' - ,fi: 'Kielletty' + ,fi: 'Et ole autentikoitunut' ,nb: 'Uautorisert' ,pl: 'Nieuwierzytelniono' ,ru: 'Не авторизовано' ,sk: 'Neautorizované' ,nl: 'Ongeauthoriseerd' ,ko: '미인증' + ,tr: 'Yetkisiz' ,zh_cn: '未授权' ,zh_tw: '未授權' } @@ -3896,6 +4293,7 @@ function init() { ,hr: 'Neuspjeli unos podataka' ,sv: 'Lägga till post nekas' ,it: 'Voce del Registro fallita' + ,ja: '入力されたものは記録できませんでした' ,dk: 'Tilføjelse af post fejlede' ,fi: 'Tiedon tallentaminen epäonnistui' ,nb: 'Lagring feilet' @@ -3904,6 +4302,7 @@ function init() { ,sk: 'Zadanie záznamu zlyhalo' ,nl: 'Toevoegen mislukt' ,ko: '입력 실패' + ,tr: 'Kayıt girişi başarısız oldu' ,zh_cn: '输入记录失败' } ,'Device authenticated' : { @@ -3919,14 +4318,16 @@ function init() { ,bg: 'Устройстово е разпознато' ,hr: 'Uređaj autenticiran' ,it: 'Disp. autenticato' + ,ja: '機器は認証されました。' ,dk: 'Enhed godkendt' ,fi: 'Laite autentikoitu' ,nb: 'Enhet godkjent' ,pl: 'Urządzenie uwierzytelnione' - ,ru: 'Устройство определено' + ,ru: 'Устройство авторизовано' ,sk: 'Zariadenie overené' ,nl: 'Apparaat geauthenticeerd' ,ko: '기기 인증' + ,tr: 'Cihaz kimliği doğrulandı' ,zh_cn: '设备已认证' ,zh_tw: '設備已認證' } @@ -3943,14 +4344,16 @@ function init() { ,bg: 'Устройсройството не е разпознато' ,hr: 'Uređaj nije autenticiran' ,it: 'Disp. non autenticato' + ,ja: '機器は認証されていません。' ,dk: 'Enhed ikke godkendt' ,fi: 'Laite ei ole autentikoitu' ,nb: 'Enhet ikke godkjent' ,pl: 'Urządzenie nieuwierzytelnione' - ,ru: 'Устройство не определено' + ,ru: 'Устройство не авторизовано' ,sk: 'Zariadenie nieje overené' ,nl: 'Apparaat niet geauthenticeerd' ,ko: '미인증 기기' + ,tr: 'Cihaz kimliği doğrulanmamış' ,zh_cn: '设备未认证' ,zh_tw: '設備未認證' } @@ -3967,6 +4370,7 @@ function init() { ,hr: 'Status autentikacije' ,sv: 'Autentiseringsstatus' ,it: 'Stato di autenticazione' + ,ja: '認証ステータス' ,dk: 'Godkendelsesstatus' ,fi: 'Autentikoinnin tila' ,nb: 'Autentiseringsstatus' @@ -3975,6 +4379,7 @@ function init() { ,sk: 'Stav overenia' ,nl: 'Authenticatie status' ,ko: '인증 상태' + ,tr: 'Kimlik doğrulama durumu' ,zh_cn: '认证状态' ,zh_tw: '認證狀態' } @@ -3991,6 +4396,7 @@ function init() { ,bg: 'Удостоверяване' ,hr: 'Autenticirati' ,it: 'Autenticare' + ,ja: '認証' ,dk: 'Godkende' ,fi: 'Autentikoi' ,nb: 'Autentiser' @@ -3999,6 +4405,7 @@ function init() { ,sk: 'Overiť' ,nl: 'Authenticatie' ,ko: '인증' + ,tr: 'Kimlik doğrulaması' ,zh_cn: '认证' ,zh_tw: '認證' } @@ -4015,6 +4422,7 @@ function init() { ,hr: 'Ukloniti' ,sv: 'Ta bort' ,it: 'Rimuovere' + ,ja: '除く' ,dk: 'Fjern' ,fi: 'Poista' ,nb: 'Slett' @@ -4023,6 +4431,7 @@ function init() { ,sk: 'Odstrániť' ,nl: 'Verwijder' ,ko: '삭제' + ,tr: 'Kaldır' ,zh_cn: '取消' ,zh_tw: '取消' } @@ -4039,14 +4448,16 @@ function init() { ,hr: 'Vaš uređaj još nije autenticiran' ,sv: 'Din enhet är ej autentiserad' ,it: 'Il tuo dispositivo non è ancora stato autenticato' + ,ja: '機器はまだ承認されていません。' ,dk: 'Din enhed er ikke godkendt endnu' ,fi: 'Laitettasi ei ole vielä autentikoitu' ,nb: 'Din enhet er ikke godkjent enda' ,pl: 'Twoje urządzenie nie jest jeszcze uwierzytelnione' - ,ru: 'Ваше устройство не опознано ' + ,ru: 'Ваше устройство еще не авторизовано ' ,sk: 'Toto zariadenie zatiaľ nebolo overené' ,nl: 'Uw apparaat is nog niet geauthenticeerd' ,ko: '당신의 기기는 아직 인증되지 않았습니다.' + ,tr: 'Cihazınız henüz doğrulanmamış' ,zh_cn: '此设备还未进行认证' ,zh_tw: '這個設備還未進行認證' } @@ -4062,6 +4473,7 @@ function init() { ,bg: 'Сензор' ,hr: 'Senzor' ,it: 'Sensore' + ,ja: 'センサー' ,dk: 'Sensor' ,fi: 'Sensori' ,nb: 'Sensor' @@ -4071,6 +4483,7 @@ function init() { ,sk: 'Senzor' ,nl: 'Sensor' ,ko: '센서' + ,tr: 'Sensor' ,zh_cn: 'CGM探头' } ,'Finger' : { @@ -4085,6 +4498,7 @@ function init() { ,bg: 'От пръстта' ,hr: 'Prst' ,it: 'Dito' + ,ja: '指' ,dk: 'Finger' ,fi: 'Sormi' ,nb: 'Finger' @@ -4094,6 +4508,7 @@ function init() { ,sk: 'Glukomer' ,nl: 'Vinger' ,ko: '손가락' + ,tr: 'Parmak' ,zh_cn: '手指' } ,'Manual' : { @@ -4108,6 +4523,7 @@ function init() { ,bg: 'Ръчно' ,hr: 'Ručno' ,it: 'Manuale' + ,ja: '手動入力' ,dk: 'Manuel' ,fi: 'Manuaalinen' ,nb: 'Manuell' @@ -4117,6 +4533,7 @@ function init() { ,sk: 'Ručne' ,nl: 'Handmatig' ,ko: '수' + ,tr: 'Elle' ,zh_cn: '手动' } ,'Scale' : { @@ -4132,6 +4549,7 @@ function init() { ,hr: 'Skala' ,sv: 'Skala' ,it: 'Scala' + ,ja: 'グラフ縦軸' ,dk: 'Skala' ,fi: 'Skaala' ,nb: 'Skala' @@ -4139,7 +4557,8 @@ function init() { ,ru: 'Масштаб' ,sk: 'Mierka' ,nl: 'Schaal' - ,ko: '규모' + ,ko: '스케일' + ,tr: 'Ölçek' ,zh_cn: '函数' ,zh_tw: '函數' } @@ -4155,6 +4574,7 @@ function init() { ,bg: 'Линейна' ,hr: 'Linearno' ,it: 'Lineare' + ,ja: '線形軸表示' ,dk: 'Lineær' ,fi: 'Lineaarinen' ,nb: 'Lineær' @@ -4164,6 +4584,7 @@ function init() { ,sk: 'Lineárne' ,nl: 'Lineair' ,ko: 'Linear' + ,tr: 'Doğrusal' //çizgisel ,zh_cn: '线性' ,zh_tw: '線性' } @@ -4179,6 +4600,7 @@ function init() { ,bg: 'Логоритмична' ,hr: 'Logaritamski' ,it: 'Logaritmica' + ,ja: '対数軸表示' ,dk: 'Logaritmisk' ,fi: 'Logaritminen' ,nb: 'Logaritmisk' @@ -4187,7 +4609,8 @@ function init() { ,ru: 'Логарифмический' ,sk: 'Logaritmické' ,nl: 'Logaritmisch' - ,ko: '다수' + ,ko: 'Logarithmic' + ,tr: 'Logaritmik' ,zh_cn: '对数' ,zh_tw: '對數' } @@ -4198,10 +4621,12 @@ function init() { ,es: 'Logarítmo (Dinámico)' ,dk: 'Logaritmisk (Dynamisk)' ,it: 'Logaritmica (Dinamica)' + ,ja: '対数軸(動的)表示' ,fr: 'Logarithmique (Dynamique)' ,el: 'Λογαριθμική (Δυναμική)' ,ro: 'Logaritmic (Dinamic)' ,bg: 'Логоритмична (Динамична)' + ,hr: 'Logaritamski (Dinamički)' ,nb: 'Logaritmisk (Dynamisk)' ,fi: 'Logaritminen (Dynaaminen)' ,sv: 'Logaritmisk (Dynamisk)' @@ -4211,6 +4636,7 @@ function init() { ,sk: 'Logaritmické (Dynamické)' ,nl: 'Logaritmisch (Dynamisch)' ,ko: '다수(동적인)' + ,tr: 'Logaritmik (Dinamik)' ,zh_cn: '对数(动态)' ,zh_tw: '對數(動態)' } @@ -4222,18 +4648,21 @@ function init() { ,fr: 'Insuline à bord' ,dk: 'Aktivt insulin (IOB)' ,it: 'IOB-Insulina a Bordo' + ,ja: 'IOB・残存インスリン' ,nb: 'AI' ,el: 'Ενεργή Ινσουλίνη (IOB)' ,ro: 'IOB-Insulină activă' ,bg: 'Активен инсулин' + ,hr: 'Aktivni inzulin' ,fi: 'Aktiivinen insuliini (IOB)' ,sv: 'Aktivt insulin (IOB)' ,pl: 'Aktywna insulina (IOB)' ,pt: 'Insulina ativa' - ,ru: 'Активный инсулин' + ,ru: 'Активный инсулин IOB' ,sk: 'Aktívny inzulín (IOB)' ,nl: 'Actieve insuline (IOB)' ,ko: 'IOB' + ,tr: 'Aktif İnsülin (IOB)' ,zh_cn: '活性胰岛素(IOB)' ,zh_tw: '活性胰島素(IOB)' } @@ -4245,41 +4674,47 @@ function init() { ,fr: 'Glucides à bord' ,dk: 'Aktive kulhydrater (COB)' ,it: 'COB-Carboidrati a Bordo' + ,ja: 'COB・残存カーボ' ,nb: 'AK' ,el: 'Ενεργοί Υδατάνθρακες (COB)' ,ro: 'COB-Carbohidrați activi' ,bg: 'Активни въглехидрати' + ,hr: 'Aktivni ugljikohidrati' ,fi: 'Aktiivinen hiilihydraatti (COB)' ,sv: 'Aktiva kolhydrater (COB)' ,pl: 'Aktywne wglowodany (COB)' ,pt: 'Carboidratos ativos' - ,ru: 'Активные углеводы' + ,ru: 'Активные углеводы COB' ,sk: 'Aktívne sacharidy (COB)' ,nl: 'Actieve koolhydraten (COB)' ,ko: 'COB' + ,tr: 'Aktif Karbonhidrat (COB)' ,zh_cn: '活性碳水化合物(COB)' ,zh_tw: '活性碳水化合物(COB)' } ,'Bolus Wizard Preview' : { cs: 'BWP-Náhled bolusového kalk.' ,he: 'סקירת אשף הבולוס' - ,de: 'Bolus-Kalkulator Vorschau' + ,de: 'Bolus-Kalkulator Vorschau (BWP)' ,es: 'Vista previa del cálculo del bolo' ,fr: 'Prévue du Calculatuer de bolus' ,dk: 'Bolus Wizard (BWP)' ,it: 'BWP-Calcolatore di bolo' + ,ja: 'BWP・ボーラスウィザード参照' ,nb: 'Boluskalkulator' ,el: 'Εργαλείο Εκτίμησης Επάρκειας Ινσουλίνης (BWP)' ,ro: 'BWP-Sugestie de bolusare' ,bg: 'Болус калкулатор' + ,hr: 'Pregled bolus čarobnjaka' ,fi: 'Aterialaskurin Esikatselu (BWP)' ,sv: 'Boluskalkylator (BWP)' ,pl: 'Kalkulator Bolusa (BWP)' ,pt: 'Ajuda de bolus' - ,ru: 'Предпросмотр мастера болюса' + ,ru: 'Калькулятор болюса' ,sk: 'Bolus Wizard' ,nl: 'Bolus Wizard Preview (BWP)' ,ko: 'Bolus 마법사 미리보기' + ,tr: 'Bolus hesaplama Sihirbazı Önizlemesi (BWP)' //İnsülin etkinlik süresi hesaplaması ,zh_cn: '大剂量向导预览(BWP)' ,zh_tw: '大劑量嚮導預覽(BWP)' } @@ -4291,10 +4726,12 @@ function init() { ,fr: 'Valeur chargée' ,dk: 'Værdi indlæst' ,it: 'Valori Caricati' + ,ja: '数値読み込み完了' ,nb: 'Verdi lastet' ,el: 'Τιμή ανακτήθηκε' ,ro: 'Valoare încărcată' ,bg: 'Стойност заредена' + ,hr: 'Vrijednost učitana' ,fi: 'Arvo ladattu' ,sv: 'Laddat värde' ,pl: 'Wartości wczytane' @@ -4302,7 +4739,8 @@ function init() { ,ru: 'Величина загружена' ,nl: 'Waarde geladen' ,sk: 'Hodnoty načítané' - ,ko: '값이 로드됨' + ,ko: '데이터가 로드됨' + ,tr: 'Yüklenen Değer' ,zh_cn: '数值已读取' ,zh_tw: '數值已讀取' } @@ -4312,20 +4750,23 @@ function init() { ,de: 'Kanülenalter' ,es: 'Antigüedad cánula' ,fr: 'Age de la canule' - ,dk: 'Insuflon alder (CAGE)' + ,dk: 'Indstik alder (CAGE)' ,it: 'CAGE-Cambio Ago' + ,ja: 'CAGE・カニューレ使用日数' ,el: 'Ημέρες Χρήσης Κάνουλας (CAGE)' ,nb: 'Nålalder' ,ro: 'CAGE-Vechime canulă' ,bg: 'Възраст на канюлата' + ,hr: 'Starost kanile' ,fi: 'Kanyylin ikä (CAGE)' ,sv: 'Kanylålder (CAGE)' ,pl: 'Czas wkłucia (CAGE)' ,pt: 'Idade da Cânula (ICAT)' - ,ru: 'Возраст канюли' + ,ru: 'Канюля отработала' ,sk: 'Zavedenie kanyly (CAGE)' ,nl: 'Canule leeftijd (CAGE)' ,ko: '캐뉼라 사용기간' + ,tr: 'Kanül yaşı' ,zh_cn: '管路使用时间(CAGE)' ,zh_tw: '管路使用時間(CAGE)' } @@ -4337,18 +4778,21 @@ function init() { ,fr: 'Profil Basal' ,dk: 'Basal profil' ,it: 'BASAL-Profilo Basale' + ,ja: 'ベーサルプロフィール' ,el: 'Προφίλ Βασικής Ινσουλίνης (BASAL)' ,nb: 'Basalprofil' ,ro: 'Profil bazală' ,bg: 'Базален профил' + ,hr: 'Bazalni profil' ,fi: 'Basaaliprofiili' ,sv: 'Basalprofil' ,pl: 'Profil dawki bazowej' ,pt: 'Perfil de Basal' - ,ru: 'Профиль Базального' + ,ru: 'Профиль Базала' ,sk: 'Bazál' ,nl: 'Basaal profiel' ,ko: 'Basal 프로파일' + ,tr: 'Bazal Profil' ,zh_cn: '基础率配置文件' ,zh_tw: '基礎率配置文件' } @@ -4365,6 +4809,7 @@ function init() { ,hr: 'Tišina 30 minuta' ,sv: 'Tyst i 30 min' ,it: 'Silenzio per 30 minuti' + ,ja: '30分静かにする' ,dk: 'Stilhed i 30 min' ,fi: 'Hiljennä 30 minuutiksi' ,nb: 'Stille i 30 min' @@ -4373,6 +4818,7 @@ function init() { ,sk: 'Stíšiť na 30 minút' ,nl: 'Sluimer 30 minuten' ,ko: '30분간 무음' + ,tr: '30 dakika sessizlik' ,zh_cn: '静音30分钟' ,zh_tw: '靜音30分鐘' } @@ -4389,6 +4835,7 @@ function init() { ,hr: 'Tišina 60 minuta' ,sv: 'Tyst i 60 min' ,it: 'Silenzio per 60 minuti' + ,ja: '60分静かにする' ,dk: 'Stilhed i 60 min' ,fi: 'Hiljennä tunniksi' ,nb: 'Stille i 60 min' @@ -4397,6 +4844,7 @@ function init() { ,sk: 'Stíšiť na 60 minút' ,nl: 'Sluimer 60 minuten' ,ko: '60분간 무음' + ,tr: '60 dakika sessizlik' ,zh_cn: '静音60分钟' ,zh_tw: '靜音60分鐘' } @@ -4413,6 +4861,7 @@ function init() { ,hr: 'Tišina 90 minuta' ,sv: 'Tyst i 90 min' ,it: 'Silenzio per 90 minuti' + ,ja: '90分静かにする' ,dk: 'Stilhed i 90 min' ,fi: 'Hiljennä 1.5 tunniksi' ,nb: 'Stille i 90 min' @@ -4421,6 +4870,7 @@ function init() { ,sk: 'Stíšiť na 90 minút' ,nl: 'Sluimer 90 minuten' ,ko: '90분간 무음' + ,tr: '90 dakika sessizlik' ,zh_cn: '静音90分钟' ,zh_tw: '靜音90分鐘' } @@ -4437,6 +4887,7 @@ function init() { ,hr: 'Tišina 120 minuta' ,sv: 'Tyst i 120 min' ,it: 'Silenzio per 120 minuti' + ,ja: '120分静かにする' ,dk: 'Stilhed i 120 min' ,fi: 'Hiljennä 2 tunniksi' ,nb: 'Stile i 120 min' @@ -4445,128 +4896,10 @@ function init() { ,sk: 'Stíšiť na 120 minút' ,nl: 'Sluimer 2 uur' ,ko: '120분간 무음' + ,tr: '120 dakika sessizlik' ,zh_cn: '静音2小时' ,zh_tw: '靜音2小時' } - ,'2HR' : { - cs: '2hod' - ,de: '2h' - ,es: '2h' - ,fr: '2hr' - ,el: '2 ώρες' - ,pt: '2h' - ,sv: '2tim' - ,ro: '2h' - ,bg: '2часа' - ,hr: '2h' - ,it: '2ORE' - ,dk: '2t' - ,fi: '2h' - ,nb: '2t' - ,pl: '2h' - ,ru: '2ч' - ,sk: '2 hod' - ,nl: '2uur' - ,ko: '2시간' - ,zh_cn: '2小时' - ,zh_tw: '2小時' - } - ,'3HR' : { - cs: '3hod' - ,he: 'שלוש שעות' - ,de: '3h' - ,es: '3h' - ,fr: '3hr' - ,el: '3 ώρες' - ,pt: '3h' - ,sv: '3tim' - ,ro: '3h' - ,bg: '3часа' - ,hr: '3h' - ,it: '3ORE' - ,dk: '3t' - ,fi: '3h' - ,nb: '3t' - ,pl: '3h' - ,ru: '3ч' - ,sk: '3 hod' - ,nl: '3uur' - ,ko: '3시간' - ,zh_cn: '3小时' - ,zh_tw: '3小時' - } - ,'6HR' : { - cs: '6hod' - ,he: 'שש שעות' - ,de: '6h' - ,es: '6h' - ,fr: '6hr' - ,el: '6 ώρες' - ,pt: '6h' - ,sv: '6tim' - ,ro: '6h' - ,bg: '6часа' - ,hr: '6h' - ,it: '6ORE' - ,dk: '6t' - ,fi: '6h' - ,nb: '6t' - ,pl: '6h' - ,ru: '6ч' - ,sk: '6 hod' - ,nl: '6uur' - ,ko: '6시간' - ,zh_cn: '6小时' - ,zh_tw: '6小時' - } - ,'12HR' : { - cs: '12hod' - ,he: 'שתים עשרה שעות' - ,de: '12h' - ,es: '12h' - ,fr: '12hr' - ,el: '12 ώρες' - ,pt: '12h' - ,sv: '12t' - ,ro: '12h' - ,bg: '12часа' - ,hr: '12h' - ,it: '12ORE' - ,dk: '12tim' - ,fi: '12h' - ,nb: '12t' - ,pl: '12h' - ,ru: '12ч' - ,sk: '12 hod' - ,nl: '12uur' - ,ko: '12시간' - ,zh_cn: '12小时' - ,zh_tw: '12小時' - } - ,'24HR' : { - cs: '24hod' - ,he: 'עשרים וארבע שעות' - ,de: '24h' - ,es: '24h' - ,fr: '24hr' - ,el: '24 ώρες' - ,pt: '24h' - ,sv: '24tim' - ,ro: '24h' - ,bg: '24часа' - ,hr: '24h' - ,it: '24ORE' - ,dk: '24t' - ,fi: '24h' - ,nb: '24t' - ,pl: '24h' - ,ru: '24ч' - ,sk: '24 hod' - ,nl: '24uur' - ,ko: '24시간' - ,zh_cn: '24小时' - ,zh_tw: '24小時' - } ,'Settings' : { cs: 'Nastavení' ,he: 'הגדרות' @@ -4580,6 +4913,7 @@ function init() { ,bg: 'Настройки' ,hr: 'Postavke' ,it: 'Impostazioni' + ,ja: '設定' ,dk: 'Indstillinger' ,fi: 'Asetukset' ,nb: 'Innstillinger' @@ -4588,6 +4922,7 @@ function init() { ,sk: 'Nastavenia' ,nl: 'Instellingen' ,ko: '설정' + ,tr: 'Ayarlar' ,zh_cn: '设置' ,zh_tw: '設置' } @@ -4612,6 +4947,7 @@ function init() { ,sk: 'Jednotky' ,nl: 'Eenheden' ,ko: '단위' + ,tr: 'Birim' //Birim Ünite ,zh_cn: '计量单位' ,zh_tw: '计量單位' } @@ -4628,6 +4964,7 @@ function init() { ,bg: 'Формат на датата' ,hr: 'Format datuma' ,it: 'Formato data' + ,ja: '日数初期化' ,dk: 'Dato format' ,fi: 'Aikamuoto' ,nb: 'Datoformat' @@ -4636,6 +4973,7 @@ function init() { ,sk: 'Formát času' ,nl: 'Datum formaat' ,ko: '날짜 형식' + ,tr: 'Veri formatı' ,zh_cn: '时间格式' ,zh_tw: '時間格式' } @@ -4652,6 +4990,7 @@ function init() { ,bg: '12 часа' ,hr: '12 sati' ,it: '12 ore' + ,ja: '12時間制' ,dk: '12 timer' ,fi: '12 tuntia' ,nb: '12 timer' @@ -4660,6 +4999,7 @@ function init() { ,sk: '12 hodín' ,nl: '12 uur' ,ko: '12 시간' + ,tr: '12 saat' ,zh_cn: '12小时制' ,zh_tw: '12小時制' } @@ -4676,6 +5016,7 @@ function init() { ,bg: '24 часа' ,hr: '24 sata' ,it: '24 ore' + ,ja: '24時間制' ,dk: '24 timer' ,fi: '24 tuntia' ,nb: '24 timer' @@ -4684,6 +5025,7 @@ function init() { ,sk: '24 hodín' ,nl: '24 uur' ,ko: '24 시간' + ,tr: '24 saat' ,zh_cn: '24小时制' ,zh_tw: '24小時制' } @@ -4699,15 +5041,17 @@ function init() { ,hr: 'Evidencija tretmana' ,sv: 'Ange händelse' ,it: 'Somministrazioni' + ,ja: '治療を記録' ,dk: 'Log en hændelse' ,fi: 'Tallenna tapahtuma' ,nb: 'Logg en hendelse' ,he: 'הזן רשומה' ,pl: 'Wprowadź leczenie' - ,ru: 'Лог лечения' + ,ru: 'Журнал лечения' ,sk: 'Záznam ošetrenia' ,nl: 'Registreer een behandeling' ,ko: 'Treatment 로그' + ,tr: 'Tedaviyi günlüğe kaydet' ,zh_cn: '记录操作' } ,'BG Check' : { @@ -4722,15 +5066,17 @@ function init() { ,bg: 'Проверка на КЗ' ,hr: 'Kontrola GUK-a' ,it: 'Controllo Glicemia' + ,ja: 'BG測定' ,dk: 'BS kontrol' ,fi: 'Verensokerin tarkistus' ,nb: 'Blodsukkerkontroll' ,he: 'בדיקת סוכר' ,pl: 'Pomiar glikemii' - ,ru: 'Контроль СК' + ,ru: 'Контроль ГК' ,sk: 'Kontrola glykémie' ,nl: 'Bloedglucose check' ,ko: '혈당 체크' + ,tr: 'KŞ Kontol' ,zh_cn: '测量血糖' } ,'Meal Bolus' : { @@ -4745,6 +5091,7 @@ function init() { ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' ,it: 'Bolo Pasto' + ,ja: '食事ボーラス' ,dk: 'Måltidsbolus' ,fi: 'Ruokailubolus' ,nb: 'Måltidsbolus' @@ -4754,6 +5101,7 @@ function init() { ,sk: 'Bolus na jedlo' ,nl: 'Maaltijd bolus' ,ko: '식사 인슐린' + ,tr: 'Yemek bolus' ,zh_cn: '正餐大剂量' } ,'Snack Bolus' : { @@ -4768,6 +5116,7 @@ function init() { ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' ,it: 'Bolo Merenda' + ,ja: '間食ボーラス' ,dk: 'Mellemmåltidsbolus' ,fi: 'Ruokakorjaus' ,nb: 'Mellommåltidsbolus' @@ -4777,6 +5126,7 @@ function init() { ,sk: 'Bolus na desiatu/olovrant' ,nl: 'Snack bolus' ,ko: '스넥 인슐린' + ,tr: 'Aperatif (Snack) Bolus' ,zh_cn: '加餐大剂量' } ,'Correction Bolus' : { @@ -4791,6 +5141,7 @@ function init() { ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' ,it: 'Bolo Correttivo' + ,ja: '補正ボーラス' ,dk: 'Korrektionsbolus' ,fi: 'Korjausbolus' ,nb: 'Korreksjonsbolus' @@ -4800,6 +5151,7 @@ function init() { ,sk: 'Korekčný bolus' ,nl: 'Correctie bolus' ,ko: '수정 인슐린' + ,tr: 'Düzeltme Bolusu' ,zh_cn: '临时大剂量' } ,'Carb Correction' : { @@ -4814,6 +5166,7 @@ function init() { ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' ,it: 'Carboidrati Correttivi' + ,ja: 'カーボ治療' ,dk: 'Kulhydratskorrektion' ,fi: 'Hiilihydraattikorjaus' ,nb: 'Karbohydratkorrigering' @@ -4823,6 +5176,7 @@ function init() { ,sk: 'Prídavok sacharidov' ,nl: 'Koolhydraat correctie' ,ko: '탄수화물 수정' + ,tr: 'Karbonhidrat Düzeltme' ,zh_cn: '碳水修正' } ,'Note' : { @@ -4837,6 +5191,7 @@ function init() { ,bg: 'Бележка' ,hr: 'Bilješka' ,it: 'Nota' + ,ja: 'メモ' ,dk: 'Note' ,fi: 'Merkintä' ,nb: 'Notat' @@ -4846,6 +5201,7 @@ function init() { ,sk: 'Poznámka' ,nl: 'Notitie' ,ko: '메모' + ,tr: 'Not' ,zh_cn: '备忘' } ,'Question' : { @@ -4860,6 +5216,7 @@ function init() { ,bg: 'Въпрос' ,hr: 'Pitanje' ,it: 'Domanda' + ,ja: '質問' ,dk: 'Spørgsmål' ,fi: 'Kysymys' ,nb: 'Spørsmål' @@ -4869,6 +5226,7 @@ function init() { ,sk: 'Otázka' ,nl: 'Vraag' ,ko: '질문' + ,tr: 'Soru' ,zh_cn: '问题' } ,'Exercise' : { @@ -4883,6 +5241,7 @@ function init() { ,bg: 'Спорт' ,hr: 'Aktivnost' ,it: 'Esercizio Fisico' + ,ja: '運動' ,dk: 'Træning' ,fi: 'Fyysinen harjoitus' ,nb: 'Trening' @@ -4892,6 +5251,7 @@ function init() { ,sk: 'Cvičenie' ,nl: 'Beweging / sport' ,ko: '운동' + ,tr: 'Egzersiz' ,zh_cn: '运动' } ,'Pump Site Change' : { @@ -4906,15 +5266,17 @@ function init() { ,bg: 'Смяна на сет' ,hr: 'Promjena seta' ,it: 'CAGE-Cambio Ago' + ,ja: 'CAGE・ポンプ注入場所変更' ,dk: 'Skift insulin infusionssted' ,fi: 'Kanyylin vaihto' ,nb: 'Pumpebytte' ,he: 'החלפת צינורית משאבה' ,pl: 'Zmiana miejsca wkłucia pompy' - ,ru: 'Смена места помпы' + ,ru: 'Смена места катетора помпы' ,sk: 'Výmena setu' ,nl: 'Nieuwe pomp infuus' ,ko: '펌프 위치 변경' + ,tr: 'Pompa Kanül değişimi' ,zh_cn: '更换胰岛素输注部位' } ,'CGM Sensor Start' : { @@ -4929,6 +5291,7 @@ function init() { ,bg: 'Ре/Стартиране на сензор' ,hr: 'Start senzora' ,it: 'CGM Avvio sensore' + ,ja: 'CGMセンサー開始' ,dk: 'Sensorstart' ,fi: 'Sensorin aloitus' ,nb: 'Sensorstart' @@ -4938,8 +5301,34 @@ function init() { ,sk: 'Spustenie senzoru' ,nl: 'CGM Sensor start' ,ko: 'CGM 센서 시작' + ,tr: 'CGM Sensörü Başlat' ,zh_cn: '启动CGM(连续血糖监测)探头' } + ,'CGM Sensor Stop' : { + cs: 'CGM Sensor Stop' + ,de: 'CGM Sensor Stop' + ,es: 'CGM Sensor Stop' + ,fr: 'CGM Sensor Stop' + ,el: 'CGM Sensor Stop' + ,pt: 'CGM Sensor Stop' + ,sv: 'CGM Sensor Stop' + ,ro: 'CGM Sensor Stop' + ,bg: 'CGM Sensor Stop' + ,hr: 'CGM Sensor Stop' + ,it: 'CGM Sensor Stop' + ,ja: 'CGM Sensor Stop' + ,dk: 'CGM Sensor Stop' + ,fi: 'CGM Sensor Stop' + ,nb: 'CGM Sensor Stop' + ,he: 'CGM Sensor Stop' + ,pl: 'CGM Sensor Stop' + ,ru: 'Остановка сенсора' + ,sk: 'CGM Sensor Stop' + ,nl: 'CGM Sensor Stop' + ,ko: 'CGM Sensor Stop' + ,tr: 'CGM Sensor Stop' + ,zh_cn: 'CGM Sensor Stop' + } ,'CGM Sensor Insert' : { cs: 'Výměna sensoru' ,de: 'CGM Sensor Wechsel' @@ -4952,15 +5341,17 @@ function init() { ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' ,it: 'CGM Cambio sensore' + ,ja: 'CGMセンサー挿入' ,dk: 'Sensor ombytning' ,fi: 'Sensorin vaihto' ,nb: 'Sensorbytte' ,he: 'החלפת חיישן סוכר' ,pl: 'Zmiana sensora' - ,ru: 'Замена сенсора' + ,ru: 'Установка сенсора' ,sk: 'Výmena senzoru' ,nl: 'CGM sensor wissel' ,ko: 'CGM 센서 삽입' + ,tr: 'CGM Sensor yerleştir' ,zh_cn: '植入CGM(连续血糖监测)探头' } ,'Dexcom Sensor Start' : { @@ -4975,6 +5366,7 @@ function init() { ,bg: 'Ре/Стартиране на Декском сензор' ,hr: 'Start Dexcom senzora' ,it: 'Avvio sensore Dexcom' + ,ja: 'Dexcomセンサー開始' ,dk: 'Dexcom sensor start' ,fi: 'Sensorin aloitus' ,nb: 'Dexcom sensor start' @@ -4984,6 +5376,7 @@ function init() { ,sk: 'Spustenie senzoru DEXCOM' ,nl: 'Dexcom sensor start' ,ko: 'Dexcom 센서 시작' + ,tr: 'Dexcom Sensör Başlat' ,zh_cn: '启动Dexcom探头' } ,'Dexcom Sensor Change' : { @@ -4998,6 +5391,7 @@ function init() { ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' ,it: 'Cambio sensore Dexcom' + ,ja: 'Dexcomセンサー挿入' ,dk: 'Dexcom sensor ombytning' ,fi: 'Sensorin vaihto' ,nb: 'Dexcom sensor bytte' @@ -5007,6 +5401,7 @@ function init() { ,sk: 'Výmena senzoru DEXCOM' ,nl: 'Dexcom sensor wissel' ,ko: 'Dexcom 센서 교체' + ,tr: 'Dexcom Sensör değiştir' ,zh_cn: '更换Dexcom探头' } ,'Insulin Cartridge Change' : { @@ -5021,6 +5416,7 @@ function init() { ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' ,it: 'Cambio cartuccia insulina' + ,ja: 'インスリンリザーバー交換' ,dk: 'Skift insulin beholder' ,fi: 'Insuliinisäiliön vaihto' ,nb: 'Skifte insulin beholder' @@ -5030,6 +5426,7 @@ function init() { ,sk: 'Výmena inzulínu' ,nl: 'Insuline cartridge wissel' ,ko: '인슐린 카트리지 교체' + ,tr: 'İnsülin rezervuar değişimi' ,zh_cn: '更换胰岛素储液器' } ,'D.A.D. Alert' : { @@ -5044,6 +5441,7 @@ function init() { ,bg: 'Сигнал от обучено куче' ,hr: 'Obavijest dijabetičkog psa' ,it: 'Allarme D.A.D.(Diabete Alert Dog)' + ,ja: 'メディカルアラート犬(DAD)の知らせ' ,dk: 'Vuf Vuf! (Diabeteshundealarm!)' ,fi: 'Diabeteskoirahälytys' ,nb: 'Diabeteshundalarm' @@ -5053,6 +5451,7 @@ function init() { ,sk: 'Upozornenie signálneho psa' ,nl: 'Hulphond waarschuwing' ,ko: 'D.A.D(Diabetes Alert Dog) 알림' + ,tr: 'D.A.D(Diabetes Alert Dog)' ,zh_cn: 'D.A.D(低血糖通报犬)警告' } ,'Glucose Reading' : { @@ -5072,10 +5471,11 @@ function init() { ,nb: 'Blodsukkermåling' ,he: 'מדידת סוכר' ,pl: 'Odczyt glikemii' - ,ru: 'Сахар крови' + ,ru: 'Значение ГК' ,sk: 'Hodnota glykémie' ,nl: 'Glucose meting' ,ko: '혈당 읽기' + ,tr: 'Glikoz Değeri' ,zh_cn: '血糖数值' } ,'Measurement Method' : { @@ -5090,6 +5490,7 @@ function init() { ,bg: 'Метод на измерване' ,hr: 'Metoda mjerenja' ,it: 'Metodo di misurazione' + ,ja: '測定方法' ,dk: 'Målemetode' ,fi: 'Mittaustapa' ,nb: 'Målemetode' @@ -5099,6 +5500,7 @@ function init() { ,sk: 'Metóda merania' ,nl: 'Meetmethode' ,ko: '측정 방법' + ,tr: 'Ölçüm Metodu' ,zh_cn: '测量方法' } ,'Meter' : { @@ -5113,7 +5515,8 @@ function init() { ,bg: 'Глюкомер' ,hr: 'Glukometar' ,it: 'Glucometro' - ,dk: 'Glukosemeter' + ,ja: '血糖測定器' + ,dk: 'Blodsukkermåler' ,fi: 'Sokerimittari' ,nb: 'Måleapparat' ,he: 'מד סוכר' @@ -5122,6 +5525,7 @@ function init() { ,sk: 'Glukomer' ,nl: 'Glucosemeter' ,ko: '혈당 측정기' + ,tr: 'Glikometre' ,zh_cn: '血糖仪' } ,'Insulin Given' : { @@ -5136,6 +5540,7 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina iznulina' ,it: 'Insulina' + ,ja: '投与されたインスリン' ,dk: 'Insulin dosis' ,fi: 'Insuliiniannos' ,nb: 'Insulin' @@ -5145,6 +5550,7 @@ function init() { ,sk: 'Podaný inzulín' ,nl: 'Toegediende insuline' ,ko: '인슐린 요구량' + ,tr: 'Verilen İnsülin' ,zh_cn: '胰岛素输注量' } ,'Amount in grams' : { @@ -5159,6 +5565,7 @@ function init() { ,bg: 'К-во в грамове' ,hr: 'Količina u gramima' ,it: 'Quantità in grammi' + ,ja: 'グラム換算' ,dk: 'Antal gram' ,fi: 'Määrä grammoissa' ,nb: 'Antall gram' @@ -5168,6 +5575,7 @@ function init() { ,sk: 'Množstvo v gramoch' ,nl: 'Hoeveelheid in gram' ,ko: '합계(grams)' + ,tr: 'Gram cinsinden miktar' ,zh_cn: '总量(g)' } ,'Amount in units' : { @@ -5182,6 +5590,7 @@ function init() { ,hr: 'Količina u jedinicama' ,sv: 'Antal enheter' ,it: 'Quantità in unità' + ,ja: '単位換算' ,dk: 'Antal enheder' ,fi: 'Annos yksiköissä' ,nb: 'Antall enheter' @@ -5191,6 +5600,7 @@ function init() { ,sk: 'Množstvo v jednotkách' ,nl: 'Aantal in eenheden' ,ko: '합계(units)' + ,tr: 'Birim miktarı' ,zh_cn: '总量(U)' } ,'View all treatments' : { @@ -5205,15 +5615,17 @@ function init() { ,bg: 'Преглед на всички събития' ,hr: 'Prikaži sve tretmane' ,it: 'Visualizza tutti le somministrazioni' + ,ja: '全治療内容を参照' ,dk: 'Vis behandlinger' ,fi: 'Katso kaikki hoitotoimenpiteet' ,nb: 'Vis behandlinger' ,he: 'הצג את כל הטיפולים' ,pl: 'Pokaż całość leczenia' - ,ru: 'Показать все события по уходу' + ,ru: 'Показать все события' ,sk: 'Zobraziť všetky ošetrenia' ,nl: 'Bekijk alle behandelingen' ,ko: '모든 treatments 보기' + ,tr: 'Tüm tedavileri görüntüle' ,zh_cn: '查看所有操作' } ,'Enable Alarms' : { @@ -5228,6 +5640,7 @@ function init() { ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' ,it: 'Attiva Allarme' + ,ja: 'アラームを有効にする' ,dk: 'Aktivere alarmer' ,fi: 'Aktivoi hälytykset' ,nb: 'Aktiver alarmer' @@ -5237,32 +5650,48 @@ function init() { ,sk: 'Aktivovať alarmy' ,nl: 'Alarmen aan!' ,ko: '알람 켜기' + ,tr: 'Alarmları Etkinleştir' ,zh_cn: '启用报警' ,zh_tw: '啟用報警' } ,'Pump Battery Change' : { nl: 'Pompbatterij vervangen' ,sv: 'Byte av pumpbatteri' + ,dk: 'Udskift pumpebatteri' ,de: 'Pumpenbatterie wechseln' ,fi: 'Pumpun patterin vaihto' ,bg: 'Смяна на батерия на помпата' + ,hr: 'Zamjena baterije pumpe' + ,ja: 'ポンプバッテリー交換' ,pl: 'Zmiana baterii w pompie' + ,ru: 'замена батареи помпы' + ,tr: 'Pompa pil değişimi' } ,'Pump Battery Low Alarm' : { nl: 'Pompbatterij bijna leeg Alarm' ,sv: 'Pumpbatteri lågt Alarm' + ,dk: 'Pumpebatteri lav Alarm' ,de: 'Pumpenbatterie niedrig Alarm' ,fi: 'Varoitus! Pumpun patteri loppumassa' ,bg: 'Аларма за слаба батерия на помпата' + ,hr: 'Upozorenje slabe baterije pumpe' + ,ja: 'ポンプバッテリーが低下' ,pl: 'Alarm! Niski poziom baterii w pompie' + ,ru: 'Внимание! низкий заряд батареи помпы' + ,tr: 'Pompa Düşük pil alarmı' } ,'Pump Battery change overdue!' : { // batteryage.js nl: 'Pompbatterij moet vervangen worden!' ,sv: 'Pumpbatteriet måste bytas!' + ,dk: 'Pumpebatteri skal skiftes!' ,de: 'Pumpenbatterie Wechsel überfällig!' ,fi: 'Pumpun patterin vaihto myöhässä!' ,bg: 'Смяната на батерията на помпата - наложителна' + ,hr: 'Prošao je rok za zamjenu baterije pumpe!' + ,ja: 'ポンプバッテリー交換期限切れてます!' , pl: 'Bateria pompy musi być wymieniona!' + ,ru: 'пропущен срок замены батареи!' + ,tr: 'Pompa pil değişimi gecikti!' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -5276,6 +5705,7 @@ function init() { ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' ,it: 'Quando si attiva un allarme acustico.' + ,ja: '有効にすればアラームが鳴動します。' ,dk: 'Når aktiveret kan alarm lyde' ,fi: 'Aktivointi mahdollistaa äänihälytykset' ,nb: 'Når aktivert er alarmer aktive' @@ -5285,6 +5715,7 @@ function init() { ,sk: 'Pri aktivovanom alarme znie zvuk ' ,nl: 'Als ingeschakeld kan alarm klinken' ,ko: '알림을 활성화 하면 알람이 울립니다.' + ,tr: 'Etkinleştirilirse, alarm çalar.' ,zh_cn: '启用后可发出声音报警' ,zh_tw: '啟用後可發出聲音報警' } @@ -5300,7 +5731,8 @@ function init() { ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' ,it: 'Urgente:Glicemia Alta' - ,dk: 'Kritisk grænse overskredet' + ,ja: '緊急高血糖アラーム' + ,dk: 'Kritisk høj grænse overskredet' ,fi: 'Kriittinen korkea' ,nb: 'Kritisk høy alarm' ,he: 'התראת גבוה דחופה' @@ -5309,6 +5741,7 @@ function init() { ,sk: 'Naliehavý alarm vysokej glykémie' ,nl: 'Urgent Alarm Hoge BG' ,ko: '긴급 고혈당 알람' + ,tr: 'Acil Yüksek Alarm' //Dikkat yüksek alarm ,zh_cn: '血糖过高报警' ,zh_tw: '血糖過高報警' } @@ -5324,6 +5757,7 @@ function init() { ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' ,it: 'Glicemia Alta' + ,ja: '高血糖アラーム' ,dk: 'Høj grænse overskredet' ,fi: 'Korkea verensokeri' ,nb: 'Høy alarm' @@ -5333,6 +5767,7 @@ function init() { ,sk: 'Alarm vysokej glykémie' ,nl: 'Alarm hoge BG' ,ko: '고혈당 알람' + ,tr: 'Yüksek Alarmı' ,zh_cn: '高血糖报警' ,zh_tw: '高血糖報警' } @@ -5348,6 +5783,7 @@ function init() { ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' ,it: 'Glicemia bassa' + ,ja: '低血糖アラーム' ,dk: 'Lav grænse overskredet' ,fi: 'Matala verensokeri' ,nb: 'Lav alarm' @@ -5357,6 +5793,7 @@ function init() { ,sk: 'Alarm nízkej glykémie' ,nl: 'Alarm lage BG' ,ko: '저혈당 알람' + ,tr: 'Düşük Alarmı' ,zh_cn: '低血糖报警' ,zh_tw: '低血糖報警' } @@ -5372,6 +5809,7 @@ function init() { ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' ,it: 'Urgente:Glicemia Bassa' + ,ja: '緊急低血糖アラーム' ,dk: 'Advarsel: Lav' ,fi: 'Kriittinen matala' ,nb: 'Kritisk lav alarm' @@ -5381,6 +5819,7 @@ function init() { ,sk: 'Naliehavý alarm nízkej glykémie' ,nl: 'Urgent Alarm lage BG' ,ko: '긴급 저혈당 알람' + ,tr: 'Acil Düşük Alarmı' ,zh_cn: '血糖过低报警' ,zh_tw: '血糖過低報警' } @@ -5397,6 +5836,7 @@ function init() { ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' ,it: 'Notifica Dati' + ,ja: '注意:古いデータ' ,dk: 'Advarsel: Gamle data' ,fi: 'Vanhat tiedot: varoitus' ,nb: 'Advarsel: Gamle data' @@ -5405,6 +5845,7 @@ function init() { ,sk: 'Varovanie: Zastaralé dáta' ,nl: 'Waarschuwing Oude gegevens na' ,ko: '손실 데이터 : 경고' + ,tr: 'Eski Veri: Uyarı' //Uyarı: veri artık geçerli değil ,zh_cn: '数据过期:提醒' ,zh_tw: '數據過期:提醒' } @@ -5421,6 +5862,7 @@ function init() { ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' ,it: 'Notifica:Urgente' + ,ja: '緊急:古いデータ' ,dk: 'Kritisk: Gamle data' ,fi: 'Vanhat tiedot: hälytys' ,nb: 'Advarsel: Veldig gamle data' @@ -5429,6 +5871,7 @@ function init() { ,sk: 'Naliehavé: Zastaralé dáta' ,nl: 'Urgente Waarschuwing Oude gegevens na' ,ko: '손실 데이터 : 긴급' + ,tr: 'Eski Veri: Acil' ,zh_cn: '数据过期:警告' ,zh_tw: '數據過期:警告' } @@ -5445,6 +5888,7 @@ function init() { ,bg: 'мин' ,hr: 'min' ,it: 'min' + ,ja: '分' ,dk: 'min' ,fi: 'minuuttia' ,nb: 'min' @@ -5453,6 +5897,7 @@ function init() { ,sk: 'min.' ,nl: 'minuten' ,ko: '분' + ,tr: 'dk.' ,zh_cn: '分' ,zh_tw: '分' } @@ -5469,7 +5914,8 @@ function init() { ,bg: 'Нощен режим' ,hr: 'Noćni način' ,it: 'Modalità Notte' - ,dk: 'Nat mode' + ,ja: '夜間モード' + ,dk: 'Nat tilstand' ,fi: 'Yömoodi' ,nb: 'Nattmodus' ,pl: 'Tryb nocny' @@ -5477,6 +5923,7 @@ function init() { ,sk: 'Nočný mód' ,nl: 'Nachtstand' ,ko: '나이트 모드' + ,tr: 'Gece Modu' ,zh_cn: '夜间模式' ,zh_tw: '夜間模式' } @@ -5493,6 +5940,7 @@ function init() { ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' ,it: 'Attivandola, la pagina sarà oscurata dalle 22:00-06:00.' + ,ja: '有効にすると、ページは 夜22時から 朝6時まで単色表示になります。' ,dk: 'Når aktiveret vil denne side nedtones fra 22:00-6:00' ,fi: 'Aktivoituna sivu himmenee kello 22 ja 06 välillä' ,nb: 'Når aktivert vil denne siden nedtones fra 22:00-06:00' @@ -5501,6 +5949,7 @@ function init() { ,sk: 'Keď je povolený, obrazovka bude stlmená od 22:00 do 6:00.' ,nl: 'Scherm dimmen tussen 22:00 en 06:00' ,ko: '페이지를 켜면 오후 10시 부터 오전 6시까지 비활성화 될 것이다.' + ,tr: 'Etkinleştirildiğinde, ekran akşam 22\'den sabah 6\'ya kadar kararır.' ,zh_cn: '启用后将在夜间22点至早晨6点降低页面亮度' ,zh_tw: '啟用後將在夜間22點至早晨6點降低頁面亮度' } @@ -5517,6 +5966,7 @@ function init() { ,bg: 'Активен' ,hr: 'Aktiviraj' ,it: 'Permettere' + ,ja: '有効' ,dk: 'Aktivere' ,fi: 'Aktivoi' ,nb: 'Aktiver' @@ -5525,6 +5975,7 @@ function init() { ,sk: 'Povoliť' ,nl: 'Activeren' ,ko: '활성화' + ,tr: 'Etkinleştir' ,zh_cn: '启用' ,zh_tw: '啟用' } @@ -5541,14 +5992,16 @@ function init() { ,bg: 'Показвай RAW данни' ,hr: 'Prikazuj sirove podatke o GUK-u' ,it: 'Mostra dati Raw BG' - ,dk: 'Vis rå data' + ,ja: '素のBGデータを表示する' + ,dk: 'Vis rå BS data' ,fi: 'Näytä raaka VS tieto' ,nb: 'Vis rådata' ,pl: 'Wyświetl surowe dane RAW' - ,ru: 'Показывать данные RAW' + ,ru: 'Показывать необработанные RAW данные' ,sk: 'Zobraziť RAW dáta' ,nl: 'Laat ruwe data zien' ,ko: 'Raw 혈당 데이터 보기' + ,tr: 'Ham KŞ verilerini göster' ,zh_cn: '显示原始血糖数据' ,zh_tw: '顯示原始血糖數據' } @@ -5565,6 +6018,7 @@ function init() { ,bg: 'Никога' ,hr: 'Nikad' ,it: 'Mai' + ,ja: '決して' ,dk: 'Aldrig' ,fi: 'Ei koskaan' ,nb: 'Aldri' @@ -5573,6 +6027,7 @@ function init() { ,sk: 'Nikdy' ,nl: 'Nooit' ,ko: '보지 않기' + ,tr: 'Hiçbir zaman' //Asla ,zh_cn: '不显示' ,zh_tw: '不顯示' } @@ -5589,6 +6044,7 @@ function init() { ,bg: 'Винаги' ,hr: 'Uvijek' ,it: 'Sempre' + ,ja: 'いつも' ,dk: 'Altid' ,fi: 'Aina' ,nb: 'Alltid' @@ -5597,6 +6053,7 @@ function init() { ,sk: 'Vždy' ,nl: 'Altijd' ,ko: '항상' + ,tr: 'Her zaman' ,zh_cn: '一直显示' ,zh_tw: '一直顯示' } @@ -5613,6 +6070,7 @@ function init() { ,bg: 'Когато има шум' ,hr: 'Kad postoji šum' ,it: 'Quando vi è rumore' + ,ja: '測定不良があった時' ,dk: 'Når der er støj' ,fi: 'Signaalihäiriöiden yhteydessä' ,nb: 'Når det er støy' @@ -5621,6 +6079,7 @@ function init() { ,sk: 'Pri šume' ,nl: 'Bij ruis' ,ko: '노이즈가 있을 때' + ,tr: 'Gürültü olduğunda' ,zh_cn: '当有噪声时显示' ,zh_tw: '當有噪聲時顯示' } @@ -5637,6 +6096,7 @@ function init() { ,bg: 'Когато е активно, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' + ,ja: '有効にすると、小さい白ドットが素のBGデータ用に表示されます' ,dk: 'Ved aktivering vil små hvide prikker blive vist for rå BG tal' ,fi: 'Aktivoituna raaka VS tieto piirtyy aikajanalle valkoisina pisteinä' ,nb: 'Ved aktivering vil små hvite prikker bli vist for rå BG tall' @@ -5645,6 +6105,7 @@ function init() { ,sk: 'Keď je povolené, malé bodky budú zobrazovať RAW dáta.' ,nl: 'Indien geactiveerd is ruwe data zichtbaar als witte punten' ,ko: '활성화 하면 작은 흰점들이 raw 혈당 데이터를 표시하게 될 것이다.' + ,tr: 'Etkinleştirildiğinde, ham KŞ verileri için küçük beyaz noktalar görüntülenecektir.' ,zh_cn: '启用后将使用小白点标注原始血糖数据' ,zh_tw: '啟用後將使用小白點標註原始血糖數據' } @@ -5661,6 +6122,7 @@ function init() { ,bg: 'Име на страницата' ,hr: 'Vlastiti naziv' ,it: 'Titolo personalizzato' + ,ja: 'カスタムタイトル' ,dk: 'Valgfri titel' ,fi: 'Omavalintainen otsikko' ,nb: 'Egen tittel' @@ -5669,6 +6131,7 @@ function init() { ,sk: 'Vlastný názov stránky' ,ko: '사용자 정의 제목' ,nl: 'Eigen titel' + ,tr: 'Özel Başlık' ,zh_cn: '自定义标题' ,zh_tw: '自定義標題' } @@ -5685,6 +6148,7 @@ function init() { ,hr: 'Tema' ,sv: 'Tema' ,it: 'Tema' + ,ja: 'テーマ' ,dk: 'Tema' ,fi: 'Teema' ,nb: 'Tema' @@ -5693,6 +6157,7 @@ function init() { ,sk: 'Vzhľad' ,nl: 'Thema' ,ko: '테마' + ,tr: 'Tema' ,zh_cn: '主题' ,zh_tw: '主題' } @@ -5709,6 +6174,7 @@ function init() { ,bg: 'Черно-бяла' ,hr: 'Default' ,it: 'Predefinito' + ,ja: 'デフォルト' ,dk: 'Standard' ,fi: 'Oletus' ,nb: 'Standard' @@ -5717,6 +6183,7 @@ function init() { ,sk: 'Predvolený' ,nl: 'Standaard' ,ko: '초기설정' + ,tr: 'Varsayılan' ,zh_cn: '默认' ,zh_tw: '默認' } @@ -5733,6 +6200,7 @@ function init() { ,bg: 'Цветна' ,hr: 'Boje' ,it: 'Colori' + ,ja: '色付き' ,dk: 'Farver' ,fi: 'Värit' ,nb: 'Farger' @@ -5741,6 +6209,7 @@ function init() { ,sk: 'Farebný' ,nl: 'Kleuren' ,ko: '색상' + ,tr: 'Renkler' ,zh_cn: '彩色' ,zh_tw: '彩色' } @@ -5760,11 +6229,15 @@ function init() { ,ro: 'Culori pentru cei cu deficiențe de vedere' ,ko: '색맹 친화적인 색상' ,bg: 'Цветове за далтонисти' + ,hr: 'Boje za daltoniste' ,it: 'Colori per daltonici' + ,ja: '色覚異常の方向けの色' ,fi: 'Värisokeille sopivat värit' ,zh_cn: '色盲患者可辨识的颜色' ,zh_tw: '色盲患者可辨識的顏色' ,pl: 'Kolory dla niedowidzących' + ,tr: 'Renk körü dostu görünüm' + ,ru: 'Цветовая гамма для людей с нарушениями восприятия цвета' } ,'Reset, and use defaults' : { cs: 'Vymaž a nastav výchozí hodnoty' @@ -5779,6 +6252,7 @@ function init() { ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' ,it: 'Resetta le impostazioni predefinite' + ,ja: 'リセットしてデフォルト設定を使用' ,dk: 'Returner til standardopsætning' ,fi: 'Palauta oletusasetukset' ,nb: 'Gjenopprett standardinnstillinger' @@ -5787,6 +6261,7 @@ function init() { ,sk: 'Resetovať do pôvodného nastavenia' ,nl: 'Herstel standaard waardes' ,ko: '초기화 그리고 초기설정으로 사용' + ,tr: 'Sıfırla ve varsayılanları kullan' ,zh_cn: '使用默认值重置' ,zh_tw: '使用默認值重置' } @@ -5803,6 +6278,7 @@ function init() { ,bg: 'Калибрации' ,hr: 'Kalibriranje' ,it: 'Calibrazioni' + ,ja: '較生' ,dk: 'Kalibrering' ,fi: 'Kalibraatiot' ,nb: 'Kalibreringer' @@ -5811,6 +6287,7 @@ function init() { ,sk: 'Kalibrácie' ,nl: 'Kalibraties' ,ko: '보정' + ,tr: 'Kalibrasyon' ,zh_cn: '校准' } ,'Alarm Test / Smartphone Enable' : { @@ -5826,6 +6303,7 @@ function init() { ,bg: 'Тестване на алармата / Активно за мобилни телефони' ,hr: 'Alarm test / Aktiviraj smartphone' ,it: 'Test Allarme / Abilita Smartphone' + ,ja: 'アラームテスト/スマートフォンを有効にする' ,dk: 'Alarm test / Smartphone aktiveret' ,fi: 'Hälytyksien testaus / Älypuhelimien äänet päälle' ,nb: 'Alarmtest / Smartphone aktivering' @@ -5834,6 +6312,7 @@ function init() { ,sk: 'Test alarmu' ,nl: 'Alarm test / activeer Smartphone' ,ko: '알람 테스트 / 스마트폰 활성화' + ,tr: 'Alarm Testi / Akıllı Telefon için Etkin' ,zh_cn: '报警测试/智能手机启用' ,zh_tw: '報警測試/智能手機啟用' } @@ -5850,14 +6329,16 @@ function init() { ,bg: 'Болус съветник ' ,hr: 'Bolus wizard' ,it: 'BW-Calcolatore di Bolo' + ,ja: 'ボーラスウィザード' ,dk: 'Bolusberegner' ,fi: 'Annosopas' ,nb: 'Boluskalkulator' ,pl: 'Kalkulator bolusa' - ,ru: 'Болюс Мастер' + ,ru: 'калькулятор болюса' ,sk: 'Bolusový kalkulátor' ,nl: 'Bolus calculator' ,ko: 'Bolus 마법사' + ,tr: 'Bolus Hesaplayıcısı' ,zh_cn: '大剂量向导' ,zh_tw: '大劑量嚮導' } @@ -5874,6 +6355,7 @@ function init() { ,bg: 'в бъдещето' ,hr: 'U budućnosti' ,it: 'nel futuro' + ,ja: '先の時間' ,dk: 'i fremtiden' ,fi: 'tulevaisuudessa' ,nb: 'fremtiden' @@ -5882,6 +6364,7 @@ function init() { ,sk: 'v budúcnosti' ,nl: 'In de toekomst' ,ko: '미래' + ,tr: 'gelecekte' ,zh_cn: '在未来' ,zh_tw: '在未來' } @@ -5898,14 +6381,16 @@ function init() { ,bg: 'преди време' ,hr: 'prije' ,it: 'tempo fa' + ,ja: '時間前' ,dk: 'tid siden' ,fi: 'aikaa sitten' ,nb: 'tid siden' ,pl: 'czas temu' - ,ru: 'в прошлом' + ,ru: 'времени назад' ,sk: 'čas pred' ,nl: 'tijd geleden' ,ko: '시간 전' + ,tr: 'süre önce' //yakın zamanda ,zh_cn: '在过去' ,zh_tw: '在過去' } @@ -5922,6 +6407,7 @@ function init() { ,bg: 'час по-рано' ,hr: 'sat unazad' ,it: 'ora fa' + ,ja: '時間前' ,dk: 'time siden' ,fi: 'tunti sitten' ,nb: 'Time siden' @@ -5930,6 +6416,7 @@ function init() { ,sk: 'hod. pred' ,nl: 'uur geleden' ,ko: '시간 전' + ,tr: 'saat önce' ,zh_cn: '小时前' ,zh_tw: '小時前' } @@ -5946,6 +6433,7 @@ function init() { ,bg: 'часа по-рано' ,hr: 'sati unazad' ,it: 'ore fa' + ,ja: '時間前' ,dk: 'timer siden' ,fi: 'tuntia sitten' ,nb: 'Timer siden' @@ -5954,6 +6442,7 @@ function init() { ,sk: 'hod. pred' ,nl: 'uren geleden' ,ko: '시간 전' + ,tr: 'saat önce' ,zh_cn: '小时前' ,zh_tw: '小時前' } @@ -5970,6 +6459,7 @@ function init() { ,bg: 'мин. по-рано' ,hr: 'minuta unazad' ,it: 'minuto fa' + ,ja: '分前' ,dk: 'minut siden' ,fi: 'minuutti sitten' ,nb: 'minutter siden' @@ -5978,6 +6468,7 @@ function init() { ,sk: 'min. pred' ,nl: 'm geleden' ,ko: '분 전' + ,tr: 'dk. önce' ,zh_cn: '分钟前' ,zh_tw: '分鐘前' } @@ -5994,6 +6485,7 @@ function init() { ,bg: 'мин. по-рано' ,hr: 'minuta unazad' ,it: 'minuti fa' + ,ja: '分前' ,dk: 'minutter siden' ,fi: 'minuuttia sitten' ,nb: 'minutter siden' @@ -6002,6 +6494,7 @@ function init() { ,sk: 'min. pred' ,nl: 'm geleden' ,ko: '분 전' + ,tr: 'dakika önce' ,zh_cn: '分钟前' ,zh_tw: '分鐘前' } @@ -6018,14 +6511,16 @@ function init() { ,bg: 'ден по-рано' ,hr: 'dan unazad' ,it: 'Giorno fa' + ,ja: '日前' ,dk: 'dag siden' ,fi: 'päivä sitten' ,nb: 'dag siden' ,pl: 'dzień temu' - ,ru: 'день назад' + ,ru: 'дн назад' ,sk: 'deň pred' ,nl: 'dag geleden' ,ko: '일 전' + ,tr: 'gün önce' ,zh_cn: '天前' ,zh_tw: '天前' } @@ -6042,6 +6537,7 @@ function init() { ,bg: 'дни по-рано' ,hr: 'dana unazad' ,it: 'giorni fa' + ,ja: '日前' ,dk: 'dage siden' ,fi: 'päivää sitten' ,nb: 'dager siden' @@ -6050,6 +6546,7 @@ function init() { ,sk: 'dni pred' ,nl: 'dagen geleden' ,ko: '일 전' + ,tr: 'günler önce' ,zh_cn: '天前' ,zh_tw: '天前' } @@ -6066,6 +6563,7 @@ function init() { ,bg: 'преди много време' ,hr: 'prije dosta vremena' ,it: 'Molto tempo fa' + ,ja: '前の期間' ,dk: 'længe siden' ,fi: 'Pitkän aikaa sitten' ,nb: 'lenge siden' @@ -6074,6 +6572,7 @@ function init() { ,sk: 'veľmi dávno' ,nl: 'lang geleden' ,ko: '기간 전' + ,tr: 'uzun zaman önce' ,zh_cn: '很长时间前' ,zh_tw: '很長時間前' } @@ -6090,6 +6589,7 @@ function init() { ,bg: 'Чист' ,hr: 'Čisto' ,it: 'Pulito' + ,ja: 'なし' ,dk: 'Rent' ,fi: 'Puhdas' ,nb: 'Rent' @@ -6098,6 +6598,7 @@ function init() { ,sk: 'Čistý' ,nl: 'Schoon' ,ko: 'Clean' + ,tr: 'Temiz' ,zh_cn: '无' ,zh_tw: '無' } @@ -6114,6 +6615,7 @@ function init() { ,bg: 'Лек' ,hr: 'Lagano' ,it: 'Leggero' + ,ja: '軽い' ,dk: 'Let' ,fi: 'Kevyt' ,nb: 'Lite' @@ -6122,6 +6624,7 @@ function init() { ,sk: 'Nízky' ,nl: 'Licht' ,ko: 'Light' + ,tr: 'Kolay' ,zh_cn: '轻度' ,zh_tw: '輕度' } @@ -6138,6 +6641,7 @@ function init() { ,bg: 'Среден' ,hr: 'Srednje' ,it: 'Medio' + ,ja: '中間' ,dk: 'Middel' ,fi: 'Keskiverto' ,nb: 'Middels' @@ -6146,6 +6650,7 @@ function init() { ,sk: 'Stredný' ,nl: 'Gemiddeld' ,ko: '보통' + ,tr: 'Orta' ,zh_cn: '中度' ,zh_tw: '中度' } @@ -6161,6 +6666,7 @@ function init() { ,bg: 'Висок' ,hr: 'Teško' ,it: 'Pesante' + ,ja: '重たい' ,dk: 'Meget' ,fi: 'Raskas' ,nb: 'Mye' @@ -6169,8 +6675,10 @@ function init() { ,sk: 'Veľký' ,nl: 'Zwaar' ,ko: '심한' + ,tr: 'Ağır' ,zh_cn: '重度' ,zh_tw: '嚴重' + ,he: 'כבד' } ,'Treatment type' : { cs: 'Typ ošetření' @@ -6184,6 +6692,7 @@ function init() { ,bg: 'Вид събитие' ,hr: 'Vrsta tretmana' ,it: 'Somministrazione' + ,ja: '治療タイプ' ,dk: 'Behandlingstype' ,fi: 'Hoidon tyyppi' ,nb: 'Behandlingstype' @@ -6192,7 +6701,9 @@ function init() { ,sk: 'Typ ošetrenia' ,nl: 'Type behandeling' ,ko: 'Treatment 타입' + ,tr: 'Tedavi tipi' ,zh_cn: '操作类型' + ,he: 'סוג הטיפול' } ,'Raw BG' : { cs: 'Glykémie z RAW dat' @@ -6210,10 +6721,11 @@ function init() { ,fi: 'Raaka VS' ,nb: 'RAW-BS' ,pl: 'Raw BG' - ,ru: 'необработанные данные СК' + ,ru: 'необработанные данные ГК' ,sk: 'RAW dáta glykémie' ,nl: 'Ruwe BG data' ,ko: 'Raw 혈당' + ,tr: 'Ham KŞ' ,zh_cn: '原始血糖' } ,'Device' : { @@ -6228,6 +6740,7 @@ function init() { ,hr: 'Uređaj' ,sv: 'Enhet' ,it: 'Dispositivo' + ,ja: '機器' ,dk: 'Enhed' ,fi: 'Laite' ,nb: 'Enhet' @@ -6236,7 +6749,9 @@ function init() { ,sk: 'Zariadenie' ,nl: 'Apparaat' ,ko: '기기' + ,tr: 'Cihaz' ,zh_cn: '设备' + ,he: 'התקן' } ,'Noise' : { cs: 'Šum' @@ -6251,6 +6766,7 @@ function init() { ,bg: 'Шум' ,hr: 'Šum' ,it: 'Rumore' + ,ja: '測定不良' ,dk: 'Støj' ,fi: 'Kohina' ,nb: 'Støy' @@ -6259,6 +6775,7 @@ function init() { ,sk: 'Šum' ,nl: 'Ruis' ,ko: '노이즈' + ,tr: 'parazit' // gürültü ,zh_cn: '噪声' } ,'Calibration' : { @@ -6274,6 +6791,7 @@ function init() { ,bg: 'Калибрация' ,hr: 'Kalibriranje' ,it: 'Calibratura' + ,ja: '較正' ,dk: 'Kalibrering' ,fi: 'Kalibraatio' ,nb: 'Kalibrering' @@ -6282,6 +6800,7 @@ function init() { ,sk: 'Kalibrácia' ,nl: 'Kalibratie' ,ko: '보정' + ,tr: 'Kalibrasyon' ,zh_cn: '校准' } ,'Show Plugins' : { @@ -6297,6 +6816,7 @@ function init() { ,hr: 'Prikaži plugine' ,sv: 'Visa tillägg' ,it: 'Mostra Plugin' + ,ja: 'プラグイン表示' ,dk: 'Vis plugins' ,fi: 'Näytä pluginit' ,nb: 'Vis plugins' @@ -6305,6 +6825,7 @@ function init() { ,sk: 'Zobraziť pluginy' ,nl: 'Laat Plug-Ins zien' ,ko: '플러그인 보기' + ,tr: 'Eklentileri Göster' ,zh_cn: '显示插件' ,zh_tw: '顯示插件' } @@ -6321,6 +6842,7 @@ function init() { ,hr: 'O aplikaciji' ,sv: 'Om' ,it: 'Informazioni' + ,ja: '約' ,dk: 'Om' ,fi: 'Nightscoutista' ,nb: 'Om' @@ -6328,7 +6850,8 @@ function init() { ,ru: 'О приложении' ,sk: 'O aplikácii' ,nl: 'Over' - ,ko: '대하여' + ,ko: '정보' + ,tr: 'Hakkında' ,zh_cn: '关于' ,zh_tw: '關於' } @@ -6345,6 +6868,7 @@ function init() { ,hr: 'Vrijednost u' ,sv: 'Värde om' ,it: 'Valore in' + ,ja: '数値' ,dk: 'Værdi i' ,fi: 'Arvo yksiköissä' ,nb: 'Verdi i' @@ -6353,6 +6877,7 @@ function init() { ,sk: 'Hodnota v' ,nl: 'Waarde in' ,ko: '값' + ,tr: 'Değer cinsinden' ,zh_cn: '数值' } ,'Carb Time' : { @@ -6364,9 +6889,10 @@ function init() { ,pt: 'Hora do carboidrato' ,ro: 'Ora carbohidrați' ,bg: 'Ядене след' - ,hr: 'Vrijeme unosa UH' + ,hr: 'Vrijeme unosa UGH' ,sv: 'Kolhydratstid' ,it: 'Tempo' + ,ja: 'カーボ時間' ,dk: 'Kulhydratstid' ,fi: 'Syöntiaika' ,nb: 'Karbohydrattid' @@ -6376,6 +6902,7 @@ function init() { ,sk: 'Čas jedla' ,nl: 'Koolhydraten tijd' ,ko: '탄수화물 시간' + ,tr: 'Karbonhidratların alım zamanı' ,zh_cn: '碳水时间' } ,'Language' : { @@ -6392,12 +6919,15 @@ function init() { ,pt: 'Língua' ,ro: 'Limba' ,bg: 'Език' + ,hr: 'Jezik' ,pl: 'Język' ,it: 'Lingua' + ,ja: '言語' ,ru: 'Язык' ,sk: 'Jazyk' ,nl: 'Taal' ,ko: '언어' + ,tr: 'Dil' ,zh_cn: '语言' ,zh_tw: '語言' } @@ -6412,6 +6942,7 @@ function init() { ,ro: 'Adaugă nou' ,el: 'Προσθήκη' ,bg: 'Добави нов' + ,hr: 'Dodaj novi' ,nb: 'Legg til ny' ,fi: 'Lisää uusi' ,pl: 'Dodaj nowy' @@ -6421,6 +6952,8 @@ function init() { ,nl: 'Voeg toe' ,ko: '새입력' ,it: 'Aggiungi nuovo' + ,ja: '新たに加える' + ,tr: 'Yeni ekle' ,zh_cn: '新增' } ,'g' : { // grams shortcut @@ -6433,6 +6966,7 @@ function init() { ,sv: 'g' ,ro: 'g' ,bg: 'гр' + ,hr: 'g' ,nb: 'g' ,fi: 'g' ,pl: 'g' @@ -6442,6 +6976,8 @@ function init() { ,nl: 'g' ,ko: 'g' ,it: 'g' + ,ja: 'g' + ,tr: 'g' ,zh_cn: '克' ,zh_tw: '克' } @@ -6455,6 +6991,7 @@ function init() { ,sv: 'ml' ,ro: 'ml' ,bg: 'мл' + ,hr: 'ml' ,nb: 'ml' ,fi: 'ml' ,pl: 'ml' @@ -6464,6 +7001,8 @@ function init() { ,nl: 'ml' ,ko: 'ml' ,it: 'ml' + ,ja: 'ml' + ,tr: 'ml' ,zh_cn: '毫升' ,zh_tw: '毫升' } @@ -6477,6 +7016,7 @@ function init() { ,sv: 'st' ,ro: 'buc' ,bg: 'бр' + ,hr: 'kom' ,nb: 'stk' ,fi: 'kpl' ,pl: 'cz.' @@ -6486,6 +7026,8 @@ function init() { ,nl: 'stk' ,ko: '조각 바로 가기(pieces shortcut)' ,it: 'pz' + ,ja: 'pcs' + ,tr: 'parça' ,zh_cn: '件' ,zh_tw: '件' } @@ -6495,11 +7037,12 @@ function init() { ,de: 'Mahlzeit hierher verschieben' ,es: 'Arrastre y suelte aquí alimentos' ,fr: 'Glisser et déposer repas ici ' - ,dk: 'Hiv og slip mad her' + ,dk: 'Træk og slip mad her' ,sv: 'Dra&Släpp mat här' ,ro: 'Drag&drop aliment aici' ,el: 'Σύρετε εδώ φαγητό' ,bg: 'Хвани и премести храна тук' + ,hr: 'Privuci hranu ovdje' ,nb: 'Dra og slipp mat her' ,fi: 'Pudota ruoka tähän' ,pl: 'Tutaj przesuń/upuść jedzenie' @@ -6508,6 +7051,7 @@ function init() { ,sk: 'Potiahni a pusti jedlo sem' ,ko: '음식을 여기에 드래그&드랍 해주세요.' ,it: 'Trascina&rilascia cibo qui' + ,tr: 'Yiyecekleri buraya sürükle bırak' ,zh_cn: '拖放食物到这' ,nl: 'Maaltijd naar hier verplaatsen' } @@ -6522,14 +7066,16 @@ function init() { ,dk: 'Omsorgsportal' ,ro: 'Care Portal' ,bg: 'Въвеждане на данни' + ,hr: 'Care Portal' ,nb: 'Omsorgsportal' ,fi: 'Hoidot' ,pl: 'Care Portal' ,pt: 'Care Portal' - ,ru: 'Портал назначений' + ,ru: 'Портал лечения' ,sk: 'Portál starostlivosti' ,nl: 'Zorgportaal' ,ko: 'Care Portal' + ,tr: 'Care Portal' ,zh_cn: '服务面板' ,zh_tw: '服務面板' } @@ -6544,6 +7090,7 @@ function init() { ,ro: 'Mediu/Necunoscut' ,el: 'Μέσος/Άγνωστος' ,bg: 'Среден/неизвестен' + ,hr: 'Srednji/Nepoznat' ,nb: 'Medium/ukjent' ,fi: 'Keskiarvo/Ei tiedossa' ,pl: 'Średni/nieznany' @@ -6553,6 +7100,7 @@ function init() { ,nl: 'Medium/Onbekend' ,ko: '보통/알려지지 않은' ,it: 'Media/Sconosciuto' + ,tr: 'Orta/Bilinmeyen' ,zh_cn: '中等/不知道' } ,'IN THE FUTURE' : { @@ -6566,6 +7114,7 @@ function init() { ,dk: 'I fremtiden' ,el: 'ΣΤΟ ΜΕΛΛΟΝ' ,bg: 'В БЪДЕШЕТО' + ,hr: 'U BUDUĆNOSTI' ,nb: 'I fremtiden' ,fi: 'TULEVAISUUDESSA' ,pl: 'W PRZYSZŁOŚCI' @@ -6575,6 +7124,7 @@ function init() { ,nl: 'IN DE TOEKOMST' ,ko: '미래' ,it: 'NEL FUTURO' + ,tr: 'GELECEKTE' ,zh_cn: '在未来' } ,'Update' : { // Update button @@ -6590,13 +7140,15 @@ function init() { ,el: 'Ενημέρωση' ,ro: 'Actualizare' ,bg: 'Актуализирай' + ,hr: 'Osvježi' ,it: 'Aggiornamento' ,pl: 'Aktualizacja' - ,fi: 'Päivitys' + ,fi: 'Tallenna' ,ru: 'Обновить' ,sk: 'Aktualizovať' ,nl: 'Update' ,ko: '업데이트' + ,tr: 'Güncelleştirme' ,zh_cn: '更新认证状态' ,zh_tw: '更新認證狀態' } @@ -6612,6 +7164,7 @@ function init() { ,el: 'Σειρά κατάταξης' ,ro: 'Sortare' ,bg: 'Ред' + ,hr: 'Sortiranje' ,it: 'Ordina' ,pl: 'Kolejność' ,pt: 'Ordenar' @@ -6620,6 +7173,7 @@ function init() { ,sk: 'Usporiadať' ,nl: 'Sortering' ,ko: '순서' + ,tr: 'Sıra' ,zh_cn: '排序' } ,'oldest on top' : { @@ -6628,12 +7182,13 @@ function init() { ,de: 'älteste oben' ,es: 'Más antiguo arriba' ,fr: 'Plus vieux en tête de liste' - ,dk: 'ældste øverst' + ,dk: 'ældste først' ,sv: 'Äldst först' ,nb: 'Eldste først' ,el: 'τα παλαιότερα πρώτα' ,ro: 'mai vechi primele' ,bg: 'Старите най-отгоре' + ,hr: 'najstarije na vrhu' ,it: 'più vecchio in alto' ,pl: 'Najstarszy na górze' ,pt: 'mais antigos no topo' @@ -6642,6 +7197,7 @@ function init() { ,sk: 'najstaršie hore' ,nl: 'Oudste boven' ,ko: '오래된 것 부터' + ,tr: 'en eski üste' ,zh_cn: '按时间升序排列' } ,'newest on top' : { @@ -6651,11 +7207,12 @@ function init() { ,de: 'neueste oben' ,es: 'Más nuevo arriba' ,fr: 'Nouveaux en tête de liste' - ,dk: 'nyeste øverst' + ,dk: 'nyeste først' ,nb: 'Nyeste først' ,el: 'τα νεότερα πρώτα' ,ro: 'mai noi primele' ,bg: 'Новите най-отгоре' + ,hr: 'najnovije na vrhu' ,it: 'più recente in alto' ,pl: 'Najnowszy na górze' ,pt: 'Mais recentes no topo' @@ -6664,12 +7221,14 @@ function init() { ,sk: 'najnovšie hore' ,nl: 'Nieuwste boven' ,ko: '새로운 것 부터' + ,tr: 'en yeni üste' ,zh_cn: '按时间降序排列' } ,'All sensor events' : { cs: 'Všechny události sensoru' ,he: 'כל האירועים חיישן' ,sv: 'Alla sensorhändelser' + ,dk: 'Alle sensorhændelser' ,nb: 'Alle sensorhendelser' ,de: 'Alle Sensor-Ereignisse' ,es: 'Todos los eventos del sensor' @@ -6678,6 +7237,7 @@ function init() { ,el: 'Όλα τα συμβάντα του αισθητήρα' ,ro: 'Evenimente legate de senzor' ,bg: 'Всички събития от сензора' + ,hr: 'Svi događaji senzora' ,it: 'Tutti gli eventi del sensore' ,fi: 'Kaikki sensorin tapahtumat' ,pl: 'Wszystkie zdarzenia sensora' @@ -6686,6 +7246,7 @@ function init() { ,sk: 'Všetky udalosti senzoru' ,nl: 'Alle sensor gegevens' ,ko: '모든 센서 이벤트' + ,tr: 'Tüm sensör olayları' ,zh_cn: '所有探头事件' } ,'Remove future items from mongo database' : { @@ -6700,6 +7261,7 @@ function init() { ,ro: 'Șterge date din viitor din baza de date mongo' ,sv: 'Ta bort framtida händelser från mongodatabasen' ,bg: 'Премахни бъдещите точки от Монго базата с данни' + ,hr: 'Obriši buduće zapise iz baze podataka' ,it: 'Rimuovere gli oggetti dal database di mongo in futuro' ,fi: 'Poista tapahtumat mongo-tietokannasta' ,pl: 'Usuń przyszłe/błędne wpisy z bazy mongo' @@ -6708,6 +7270,7 @@ function init() { ,sk: 'Odobrať budúce položky z Mongo databázy' ,nl: 'Verwijder items die in de toekomst liggen uit database' ,ko: 'mongo DB에서 미래 항목들을 지우세요.' + ,tr: 'Gelecekteki öğeleri mongo veritabanından kaldır' ,zh_cn: '从数据库中清除所有未来条目' ,zh_tw: '從數據庫中清除所有未來條目' } @@ -6723,6 +7286,7 @@ function init() { ,ro: 'Caută și elimină tratamente din viitor' ,sv: 'Hitta och ta bort framtida behandlingar' ,bg: 'Намери и премахни събития в бъдещето' + ,hr: 'Nađi i obriši tretmane u budućnosti' ,it: 'Individuare e rimuovere le somministrazioni in futuro' ,fi: 'Etsi ja poista tapahtumat joiden aikamerkintä on tulevaisuudessa' ,pt: 'Encontrar e remover tratamentos futuros' @@ -6731,6 +7295,7 @@ function init() { ,sk: 'Nájsť a odstrániť záznamy ošetrenia v budúcnosti' ,nl: 'Zoek en verwijder behandelingen met datum in de toekomst' ,ko: '미래에 treatments를 검색하고 지우세요.' + ,tr: 'Gelecekte tedavileri bulun ve kaldır' ,zh_cn: '查找并清除所有未来的操作' ,zh_tw: '查找並清除所有未來的操作' } @@ -6746,6 +7311,7 @@ function init() { ,ro: 'Acest instrument curăță tratamentele din viitor.' ,sv: 'Denna uppgift hittar och rensar framtida händelser' ,bg: 'Тази опция намира и премахва събития в бъдещето.' + ,hr: 'Ovo nalazi i briše tretmane u budućnosti' ,it: 'Trovare e rimuovere le somministrazioni in futuro' ,fi: 'Tämä työkalu poistaa tapahtumat joiden aikamerkintä on tulevaisuudessa.' ,pl: 'To narzędzie znajduje i usuwa przyszłe/błędne zabiegi' @@ -6754,6 +7320,7 @@ function init() { ,sk: 'Táto úloha nájde a odstáni záznamy ošetrenia v budúcnosti.' ,nl: 'Dit commando zoekt en verwijdert behandelingen met datum in de toekomst' ,ko: '이 작업은 미래에 treatments를 검색하고 지우는 것입니다.' + ,tr: 'Bu görev gelecekte tedavileri bul ve kaldır.' ,zh_cn: '此功能查找并清除所有未来的操作。' ,zh_tw: '此功能查找並清除所有未來的操作。' } @@ -6769,6 +7336,7 @@ function init() { ,ro: 'Șterge tratamentele din viitor' ,sv: 'Ta bort framtida händelser' ,bg: 'Премахни събитията в бъдешето' + ,hr: 'Obriši tretmane u budućnosti' ,it: 'Rimuovere somministrazioni in futuro' ,fi: 'Poista tapahtumat' ,pl: 'Usuń zabiegi w przyszłości' @@ -6777,6 +7345,7 @@ function init() { ,sk: 'Odstrániť záznamy ošetrenia v budúcnosti' ,nl: 'Verwijder behandelingen met datum in de toekomst' ,ko: '미래 treatments 지우기' + ,tr: 'Gelecekte tedavileri kaldır' ,zh_cn: '清除未来操作' ,zh_tw: '清除未來操作' } @@ -6790,6 +7359,7 @@ function init() { ,nb: 'Finn og fjern fremtidige hendelser' ,el: 'Εύρεση και αφαίρεση μελλοντικών εγγραφών από τη βάση δεδομένων' ,bg: 'Намери и премахни данни от сензора в бъдещето' + ,hr: 'Nađi i obriši zapise u budućnosti' ,ro: 'Caută și elimină valorile din viitor' ,sv: 'Hitta och ta bort framtida händelser' ,it: 'Trovare e rimuovere le voci in futuro' @@ -6800,6 +7370,7 @@ function init() { ,sk: 'Nájsť a odstrániť CGM dáta v budúcnosti' ,nl: 'Zoek en verwijder behandelingen met datum in de toekomst' ,ko: '미래에 입력을 검색하고 지우세요.' + ,tr: 'Gelecekteki girdileri bul ve kaldır' ,zh_cn: '查找并清除所有的未来的记录' ,zh_tw: '查找並清除所有的未來的記錄' } @@ -6813,6 +7384,7 @@ function init() { ,es: 'Este comando encuentra y elimina datos del sensor futuros creados por actualizaciones con errores en fecha/hora' ,dk: 'Denne handling finder og fjerner CGM data i fremtiden forårsaget af indlæsning med forkert dato/tid.' ,bg: 'Тази опция ще намери и премахне данни от сензора в бъдещето, създадени поради грешна дата/време.' + ,hr: 'Ovo nalazi i briše podatke sa senzora u budućnosti nastalih s uploaderom sa krivim datumom/vremenom' ,ro: 'Instrument de căutare și eliminare a datelor din viitor, create de uploader cu ora setată greșit' ,sv: 'Denna uppgift hittar och tar bort framtida CGM-data skapad vid felaktig tidsinställning' ,it: 'Trovare e rimuovere i dati CGM in futuro creato da uploader/xdrip con data/ora sbagliato.' @@ -6823,6 +7395,7 @@ function init() { ,sk: 'Táto úloha nájde a odstráni CGM dáta v budúcnosti vzniknuté zle nastaveným časom uploaderu.' ,nl: 'Dit commando zoekt en verwijdert behandelingen met datum in de toekomst' ,ko: '이 작업은 잘못된 날짜/시간으로 업로드 되어 생성된 미래의 CGM 데이터를 검색하고 지우는 것입니다.' + ,tr: 'Yükleyicinin oluşturduğu gelecekteki CGM verilerinin yanlış tarih/saat olanlarını bul ve kaldır.' ,zh_cn: '此功能查找并清除所有上传时日期时间错误导致生成在未来时间的CGM数据。' ,zh_tw: '此功能查找並清除所有上傳時日期時間錯誤導致生成在未來時間的CGM數據。' } @@ -6836,16 +7409,18 @@ function init() { ,dk: 'Fjern indgange i fremtiden' ,el: 'Αφαίρεση μελλοντικών ενεργειών' ,bg: 'Премахни данните от сензора в бъдещето' + ,hr: 'Obriši zapise u budućnosti' ,ro: 'Elimină înregistrările din viitor' ,sv: 'Ta bort framtida händelser' ,it: 'Rimuovere le voci in futuro' ,fi: 'Poista tapahtumat' ,pl: 'Usuń wpisy w przyszłości' ,pt: 'Remover entradas futuras' - ,ru: 'Удаляются данные из будущего' + ,ru: 'Удалить данные из будущего' ,sk: 'Odstrániť CGM dáta v budúcnosti' ,nl: 'Verwijder invoer met datum in de toekomst' ,ko: '미래의 입력 지우기' + ,tr: 'Gelecekteki girdileri kaldır' ,zh_cn: '清除未来记录' ,zh_tw: '清除未來記錄' } @@ -6859,6 +7434,7 @@ function init() { ,dk: 'Indlæser database ...' ,el: 'Φόρτωση Βάσης Δεδομένων' ,bg: 'Зареждане на базата с данни ...' + ,hr: 'Učitavanje podataka' ,ro: 'Încarc baza de date' ,sv: 'Laddar databas ...' ,it: 'Carica Database ...' @@ -6869,6 +7445,7 @@ function init() { ,sk: 'Nahrávam databázu...' ,nl: 'Database laden ....' ,ko: '데이터베이스 로딩' + ,tr: 'Veritabanı yükleniyor ...' ,zh_cn: '载入数据库...' ,zh_tw: '載入數據庫...' } @@ -6884,6 +7461,7 @@ function init() { ,es: 'Base de datos contiene %1 registros futuros' ,dk: 'Databasen indeholder %1 fremtidige indgange' ,bg: 'Базата с дани съдържа %1 бъдещи записи' + ,hr: 'Baza sadrži %1 zapisa u budućnosti' ,it: 'Contiene Database %1 record futuri' ,fi: 'Tietokanta sisältää %1 merkintää tulevaisuudessa' ,pl: 'Baza danych zawiera %1 przyszłych rekordów' @@ -6892,6 +7470,7 @@ function init() { ,sk: 'Databáza obsahuje %1 záznamov v budúcnosti' ,nl: 'Database bevat %1 toekomstige data' ,ko: '데이터베이스는 미래 기록을 %1 포함하고 있습니다.' + ,tr: 'Veritabanı %1 gelecekteki girdileri içeriyor' ,zh_cn: '数据库包含%1条未来记录' ,zh_tw: '數據庫包含%1條未來記錄' } @@ -6907,6 +7486,7 @@ function init() { ,ro: 'Șterg %1 înregistrări selectate?' ,sv: 'Ta bort %1 valda händelser' ,bg: 'Премахване на %1 от избраните записи?' + ,hr: 'Obriši %1 odabrani zapis?' ,it: 'Rimuovere %1 record selezionati?' ,fi: 'Poista %1 valittua merkintää?' ,pl: 'Usunąć %1 wybranych rekordów?' @@ -6915,6 +7495,7 @@ function init() { ,sk: 'Odstrániť %1 vybraných záznamov' ,nl: 'Verwijder %1 geselecteerde data?' ,ko: '선택된 기록 %1를 지우시겠습니까?' + ,tr: 'Seçilen %1 kayıtlar kaldırılsın? ' ,zh_cn: '清除%1条选择的记录?' ,zh_tw: '清除%1條選擇的記錄?' } @@ -6930,6 +7511,7 @@ function init() { ,ro: 'Eroare la încărcarea bazei de date' ,sv: 'Fel vid laddning av databas' ,bg: 'Грешка при зареждане на базата с данни' + ,hr: 'Greška pri učitavanju podataka' ,it: 'Errore di caricamento del database' ,fi: 'Ongelma tietokannan lataamisessa' ,pl: 'Błąd wczytywania bazy danych' @@ -6938,6 +7520,7 @@ function init() { ,sk: 'Chyba pri nahrávanií databázy' ,nl: 'Fout bij het laden van database' ,ko: '데이터베이스 로딩 에러' + ,tr: 'Veritabanını yüklerken hata oluştu' ,zh_cn: '载入数据库错误' ,zh_tw: '載入數據庫錯誤' } @@ -6953,6 +7536,7 @@ function init() { ,ro: 'Înregistrarea %1 a fost ștearsă...' ,sv: 'Händelse %1 borttagen ...' ,bg: '%1 записи премахнати' + ,hr: 'Zapis %1 obrisan...' ,it: 'Record %1 rimosso ...' ,fi: 'Merkintä %1 poistettu ...' ,pl: '%1 rekordów usunięto ...' @@ -6961,6 +7545,7 @@ function init() { ,sk: '%1 záznamov bolo odstránených...' ,nl: 'Data %1 verwijderd ' ,ko: '기록 %1가 삭제되었습니다.' + ,tr: '%1 kaydı silindi ...' ,zh_cn: '%1条记录已清除' ,zh_tw: '%1條記錄已清除' } @@ -6976,6 +7561,7 @@ function init() { ,ro: 'Eroare la ștergerea înregistrării %1' ,sv: 'Fel vid borttagning av %1' ,bg: 'Грешка при премахването на %1 от записите' + ,hr: 'Greška prilikom brisanja zapisa %1' ,it: 'Errore rimozione record %1' ,fi: 'Virhe poistaessa merkintää numero %1' ,pl: 'Błąd przy usuwaniu rekordu %1' @@ -6984,6 +7570,7 @@ function init() { ,sk: 'Chyba pri odstraňovaní záznamu %1' ,nl: 'Fout bij het verwijderen van %1 data' ,ko: '기록 %1을 삭제하는 중에 에러가 발생했습니다.' + ,tr: '%1 kayıt kaldırılırken hata oluştu' ,zh_cn: '%1条记录清除出错' ,zh_tw: '%1條記錄清除出錯' } @@ -6991,7 +7578,7 @@ function init() { cs: 'Odstraňování záznamů ...' ,he: 'מוחק רשומות ... ' ,nb: 'Fjerner elementer...' - ,fr: 'Effacement d\événements...' + ,fr: 'Effacement dévénements...' ,ro: 'Se șterg înregistrările...' ,el: 'Αφαίρεση Εγγραφών' ,de: 'Entferne Einträge ...' @@ -6999,6 +7586,7 @@ function init() { ,dk: 'Sletter indgange ...' ,sv: 'Tar bort händelser ...' ,bg: 'Изтриване на записите...' + ,hr: 'Brisanje zapisa' ,it: 'Elimino dei record ...' ,fi: 'Poistan merkintöjä' ,pl: 'Usuwanie rekordów ...' @@ -7007,9 +7595,14 @@ function init() { ,sk: 'Odstraňovanie záznamov...' ,nl: 'Verwijderen van data .....' ,ko: '기록 삭제 중' + ,tr: 'Kayıtlar siliniyor ...' ,zh_cn: '正在删除记录...' ,zh_tw: '正在刪除記錄...' } + ,'%1 records deleted' : { + hr: 'obrisano %1 zapisa' + ,de: '%1 Einträge gelöscht' + } ,'Clean Mongo status database' : { cs: 'Vyčištění Mongo databáze statusů' ,he: 'נקי מסד הנתונים מצב מונגו ' @@ -7022,6 +7615,7 @@ function init() { ,dk: 'Slet Mongo status database' ,sv: 'Rensa Mongo status databas' ,bg: 'Изчисти статуса на Монго базата с данни' + ,hr: 'Obriši bazu statusa' ,it: 'Pulisci database di Mongo' ,fi: 'Siivoa statustietokanta' ,pl: 'Oczyść status bazy danych Mongo' @@ -7030,6 +7624,7 @@ function init() { ,sk: 'Vyčistiť Mongo databázu statusov' ,nl: 'Ruim Mongo database status op' ,ko: 'Mongo 상태 데이터베이스를 지우세요.' + ,tr: 'Mongo durum veritabanını temizle' ,zh_cn: '清除状态数据库' } ,'Delete all documents from devicestatus collection' : { @@ -7044,6 +7639,7 @@ function init() { ,ro: 'Șterge toate documentele din colecția de status dispozitiv' ,sv: 'Ta bort alla dokument i devicestatus collektionen' ,bg: 'Изтрий всички документи от папката статус-устройство' + ,hr: 'Obriši sve zapise o statusima' ,it: 'Eliminare tutti i documenti dalla collezione "devicestatus"' ,fi: 'Poista kaikki tiedot statustietokannasta' ,pl: 'Usuń wszystkie dokumenty z kolekcji devicestatus' @@ -7051,6 +7647,7 @@ function init() { ,ru: 'Стереть все документы из коллекции статус устройства' ,sk: 'Odstránenie všetkých záznamov z kolekcie "devicestatus"' ,ko: 'devicestatus 수집에서 모든 문서들을 지우세요' + ,tr: 'Devicestatus koleksiyonundan tüm dokümanları sil' ,zh_cn: '从设备状态采集删除所有文档' ,nl: 'Verwijder alle documenten uit "devicestatus" database' } @@ -7061,10 +7658,11 @@ function init() { ,el: 'Αυτή η ενέργεια διαγράφει όλα τα δεδομένα της κατάστασης της συσκευής ανάγνωσης. Χρήσιμη όταν η κατάσταση της συσκευής ανάγνωσης δεν ανανεώνεται σωστά.' ,de: 'Diese Aufgabe entfernt alle Dokumente aus der Gerätestatus-Sammlung. Nützlich wenn der Uploader-Batteriestatus sich nicht aktualisiert.' ,es: 'Este comando elimina todos los documentos desde la colección devicestatus. Útil cuando el estado de la batería cargadora no se actualiza correctamente' - ,dk: 'Denne handling fjerner alle dokumenter fra device status tabellen. Brugbart når uploader betteri status ikke er korrekt opdateret.' + ,dk: 'Denne handling fjerner alle dokumenter fra device status tabellen. Brugbart når uploader batteri status ikke opdateres korrekt.' ,ro: 'Acest instrument șterge toate documentele din colecția devicestatus. Se folosește când încărcarea bateriei nu se afișează corect.' ,sv: 'Denna uppgift tar bort alla dokument från devicestatuskollektionen. Användbart när batteristatus ej uppdateras' ,bg: 'Тази опция премахва всички документи от папката статус-устройство. Полезно е, когато статусът на батерията не се обновява.' + ,hr: 'Ovo briše sve zapise o statusima. Korisno kada se status baterije uploadera ne osvježava ispravno.' ,it: 'Questa attività elimina tutti i documenti dalla collezione "devicestatus". Utile quando lo stato della batteria uploader/xdrip non si aggiorna.' ,fi: 'Tämä työkalu poistaa kaikki tiedot statustietokannasta, mikä korjaa tilanteen, jossa puhelimen akun lataustilanne ei näy oikein.' ,pl: 'To narzędzie usuwa wszystkie dokumenty z kolekcji devicestatus. Potrzebne jest wtedy, gdy status baterii uploadera nie jest aktualizowany' @@ -7072,6 +7670,7 @@ function init() { ,ru: 'Эта опция удаляет все документы из коллекции статус устройства. Полезно когда состояние батвреи загрузчика не обновляется' ,sk: 'Táto úloha vymaže všetky záznamy z kolekcie "devicestatus". Je to vhodné keď sa stav batérie nezobrazuje správne.' ,ko: '이 작업은 모든 문서를 devicestatus 수집에서 지웁니다. 업로더 배터리 상태가 적절하게 업데이트 되지 않을 때 유용합니다.' + ,tr: 'Bu görev tüm durumları Devicestatus koleksiyonundan kaldırır. Yükleyici pil durumu güncellenmiyorsa kullanışlıdır.' ,zh_cn: '此功能从设备状态采集中删除所有文档。适用于上传设备电量信息不能正常同步时使用。' ,nl: 'Dit commando verwijdert alle documenten uit "devicestatus" database. Handig wanneer de batterij status niet correct wordt geupload.' } @@ -7087,6 +7686,7 @@ function init() { ,dk: 'Slet alle dokumenter' ,sv: 'Ta bort alla dokument' ,bg: 'Изтрий всички документи' + ,hr: 'Obriši sve zapise' ,it: 'Eliminare tutti i documenti' ,fi: 'Poista kaikki tiedot' ,pl: 'Usuń wszystkie dokumenty' @@ -7095,9 +7695,10 @@ function init() { ,sk: 'Zmazať všetky záznamy' ,nl: 'Verwijder alle documenten' ,ko: '모든 문서들을 지우세요' + ,tr: 'Tüm Belgeleri sil' ,zh_cn: '删除所有文档' } - ,'Delete all documents from devicestatus collection devicestatus?' : { + ,'Delete all documents from devicestatus collection?' : { cs: 'Odstranit všechny dokumenty z kolekce devicestatus?' ,he: 'מחק את כל המסמכים מרשימת סטטוס ההתקנים ' ,nb: 'Fjern alle dokumenter fra device status tabellen?' @@ -7109,6 +7710,7 @@ function init() { ,ro: 'Șterg toate documentele din colecția devicestatus?' ,sv: 'Ta bort alla dokument från devicestatuscollektionen' ,bg: 'Изтриване на всички документи от папката статус-устройство?' + ,hr: 'Obriši sve zapise statusa?' ,it: 'Eliminare tutti i documenti dalla collezione devicestatus?' ,fi: 'Poista tiedot statustietokannasta?' ,pl: 'Czy na pewno usunąć wszystkie dokumenty z kolekcji devicestatus?' @@ -7116,6 +7718,7 @@ function init() { ,ru: 'Стереть все документы коллекции статус устройства?' ,sk: 'Zmazať všetky záznamy z kolekcie "devicestatus"?' ,ko: 'devicestatus 수집의 모든 문서들을 지우세요.' + ,tr: 'Tüm Devicestatus koleksiyon belgeleri silinsin mi?' ,zh_cn: '从设备状态采集删除所有文档?' ,nl: 'Wil je alle data van "devicestatus" database verwijderen?' } @@ -7131,6 +7734,7 @@ function init() { ,ro: 'Baza de date conține %1 înregistrări' ,sv: 'Databasen innehåller %1 händelser' ,bg: 'Базата с данни съдържа %1 записи' + ,hr: 'Baza sadrži %1 zapisa' ,it: 'Contiene Database %1 record' ,fi: 'Tietokanta sisältää %1 merkintää' ,pl: 'Baza danych zawiera %1 rekordów' @@ -7139,6 +7743,7 @@ function init() { ,sk: 'Databáza obsahuje %1 záznamov' ,nl: 'Database bevat %1 gegevens' ,ko: '데이터베이스는 %1 기록을 포함합니다.' + ,tr: 'Veritabanı %1 kayıt içeriyor' ,zh_cn: '数据库包含%1条记录' } ,'All records removed ...' : { @@ -7148,11 +7753,12 @@ function init() { ,de: 'Alle Einträge entfernt...' ,es: 'Todos los registros eliminados ...' ,fr: 'Toutes les valeurs ont été effacées' - ,dk: 'Alle indgange fjernet ...' + ,dk: 'Alle hændelser fjernet ...' ,el: 'Έγινε διαγραφή όλων των δεδομένων' ,ro: 'Toate înregistrările au fost șterse.' ,sv: 'Alla händelser raderade ...' ,bg: 'Всички записи премахнати ...' + ,hr: 'Svi zapisi obrisani' ,it: 'Tutti i record rimossi ...' ,fi: 'Kaikki merkinnät poistettu ...' ,pl: 'Wszystkie rekordy usunięto' @@ -7161,8 +7767,75 @@ function init() { ,sk: 'Všetky záznamy boli zmazané...' ,nl: 'Alle gegevens verwijderd' ,ko: '모든 기록들이 지워졌습니다.' + ,tr: 'Tüm kayıtlar kaldırıldı ...' ,zh_cn: '所有记录已经被清除' } + ,'Delete all documents from devicestatus collection older than 30 days' : { + hr: 'Obriši sve statuse starije od 30 dana' + ,ru: 'Удалить все записи коллекции devicestatus' + ,de: 'Alle Dokumente der Gerätestatus-Sammlung löschen, die älter als 30 Tage sind' + } + ,'Number of Days to Keep:' : { + hr: 'Broj dana za sačuvati:' + ,ru: 'Оставить дней' + ,de: 'Daten löschen, die älter sind (in Tagen) als:' + } + ,'This task removes all documents from devicestatus collection that are older than 30 days. Useful when uploader battery status is not properly updated.' : { + hr: 'Ovo uklanja sve statuse starije od 30 dana. Korisno kada se status baterije uploadera ne osvježava ispravno.' + ,ru: 'Это удалит все документы коллекции devicestatus которым более 30 дней. Полезно, когда статус батареи не обновляется или обновляется неверно.' + ,de: 'Diese Aufgabe entfernt alle Dokumente aus der Gerätestatus-Sammlung, die älter sind als 30 Tage. Nützlich wenn der Uploader-Batteriestatus sich nicht aktualisiert.' + } + ,'Delete old documents from devicestatus collection?' : { + hr: 'Obriši stare statuse' + ,de: 'Alte Dokumente aus der Gerätestatus-Sammlung entfernen?' + + } + ,'Clean Mongo entries (glucose entries) database' : { + hr: 'Obriši GUK zapise iz baze' + ,de: 'Mongo-Einträge (Glukose-Einträge) Datenbank bereinigen' + } + ,'Delete all documents from entries collection older than 180 days' : { + hr: 'Obriši sve zapise starije od 180 dana' + ,de: 'Alle Dokumente aus der Einträge-Sammlung löschen, die älter sind als 180 Tage' + } + ,'This task removes all documents from entries collection that are older than 180 days. Useful when uploader battery status is not properly updated.' : { + hr: 'Ovo briše sve zapise starije od 180 dana. Korisno kada se status baterije uploadera ne osvježava.' + ,de: 'Diese Aufgabe entfernt alle Dokumente aus der Einträge-Sammlung, die älter sind als 180 Tage. Nützlich wenn der Uploader-Batteriestatus sich nicht aktualisiert.' + } + ,'Delete old documents' : { + hr: 'Obriši stare zapise' + ,de: 'Alte Dokumente löschen' + } + ,'Delete old documents from entries collection?' : { + hr: 'Obriši stare zapise?' + ,de: 'Alte Dokumente aus der Einträge-Sammlung entfernen?' + } + ,'%1 is not a valid number' : { + hr: '%1 nije valjan broj' + ,de: '%1 ist keine gültige Zahl' + ,he: 'זה לא מיספר %1' + } + ,'%1 is not a valid number - must be more than 2' : { + hr: '%1 nije valjan broj - mora biti veći od 2' + ,de: '%1 ist keine gültige Zahl - Eingabe muss größer als 2 sein' + } + ,'Clean Mongo treatments database' : { + hr: 'Obriši tretmane iz baze' + ,de: 'Mongo-Behandlungsdatenbank bereinigen' + } + ,'Delete all documents from treatments collection older than 180 days' : { + hr: 'Obriši tretmane starije od 180 dana iz baze' + ,de: 'Alle Dokumente aus der Behandlungs-Sammlung löschen, die älter sind als 180 Tage' + } + ,'This task removes all documents from treatments collection that are older than 180 days. Useful when uploader battery status is not properly updated.' : { + hr: 'Ovo briše sve tretmane starije od 180 dana iz baze. Korisno kada se status baterije uploadera ne osvježava.' + ,de: 'Diese Aufgabe entfernt alle Dokumente aus der Behandlungs-Sammlung, die älter sind als 180 Tage. Nützlich wenn der Uploader-Batteriestatus sich nicht aktualisiert.' + } + ,'Delete old documents from treatments collection?' : { + hr: 'Obriši stare tretmane?' + ,ru: 'Удалить старые документы из коллекции лечения?' + ,de: 'Alte Dokumente aus der Behandlungs-Sammlung entfernen?' + } ,'Admin Tools' : { cs: 'Nástroje pro správu' ,he: 'כלי אדמיניסטרציה ' @@ -7174,7 +7847,9 @@ function init() { ,dl: 'Administrator opgaver' ,el: 'Εργαλεία Διαχειριστή' ,sv: 'Adminverktyg' + ,dk: 'Administrations værktøj' ,bg: 'Настройки на администратора' + ,hr: 'Administracija' ,it: 'NS - Dati Mongo' ,fi: 'Ylläpitotyökalut' ,pl: 'Narzędzia administratora' @@ -7183,6 +7858,7 @@ function init() { ,sk: 'Nástroje pre správu' ,nl: 'Admin tools' ,ko: '관리 도구' + ,tr: 'Yönetici araçları' ,zh_cn: '管理工具' ,zh_tw: '管理工具' } @@ -7198,6 +7874,7 @@ function init() { ,dk: 'Nightscout - rapporter' ,sv: 'Nightscout - Statistik' ,bg: 'Найтскаут статистика' + ,hr: 'Nightscout izvješća' ,it: 'Nightscout - Statistiche' ,fi: 'Nightscout raportointi' ,pl: 'Nightscout - raporty' @@ -7205,7 +7882,8 @@ function init() { ,ru: 'Статистика Nightscout' ,sk: 'Nightscout výkazy' ,nl: 'Nightscout rapportages' - ,ko: 'Nightscout 보고하기' + ,ko: 'Nightscout 보고서' + ,tr: 'NightScout raporları' ,zh_cn: 'Nightscout报表生成器' } ,'Cancel' : { @@ -7220,7 +7898,9 @@ function init() { ,ro: 'Renunță' ,sv: 'Avbryt' ,bg: 'Откажи' + ,hr: 'Odustani' ,it: 'Cancellare' + ,ja: '中止' ,fi: 'Peruuta' ,pl: 'Anuluj' ,pt: 'Cancelar' @@ -7228,6 +7908,7 @@ function init() { ,sk: 'Zrušiť' ,nl: 'Annuleer' ,ko: '취소' + ,tr: 'İptal' ,zh_cn: '取消' } ,'Edit treatment' : { @@ -7238,10 +7919,11 @@ function init() { ,de: 'Bearbeite Behandlung' ,es: 'Editar tratamiento' ,fr: 'Modifier un traitement' - ,dk: 'Rediger indgang' + ,dk: 'Rediger behandling' ,el: 'Επεξεργασία Εγγραφής' ,sv: 'Redigera behandling' ,bg: 'Редакция на събитие' + ,hr: 'Uredi tretman' ,it: 'Modifica Somministrazione' ,fi: 'Muuta merkintää' ,pl: 'Edytuj zabieg' @@ -7250,6 +7932,7 @@ function init() { ,sk: 'Upraviť ošetrenie' ,nl: 'Bewerkt behandeling' ,ko: 'Treatments 편집' + ,tr: 'Tedaviyi düzenle' ,zh_cn: '编辑操作' } ,'Duration' : { @@ -7263,6 +7946,7 @@ function init() { ,el: 'Διάρκεια' ,sv: 'Varaktighet' ,bg: 'Времетраене' + ,hr: 'Trajanje' ,it: 'Tempo' ,nb: 'Varighet' ,fi: 'Kesto' @@ -7272,6 +7956,7 @@ function init() { ,sk: 'Trvanie' ,nl: 'Duur' ,ko: '기간' + ,tr: 'süre' ,zh_cn: '持续' } ,'Duration in minutes' : { @@ -7285,6 +7970,7 @@ function init() { ,el: 'Διάρκεια σε λεπτά' ,sv: 'Varaktighet i minuter' ,bg: 'Времетраене в мин.' + ,hr: 'Trajanje u minutama' ,it: 'Tempo in minuti' ,nb: 'Varighet i minutter' ,fi: 'Kesto minuuteissa' @@ -7294,6 +7980,7 @@ function init() { ,sk: 'Trvanie v minútach' ,nl: 'Duur in minuten' ,ko: '분당 지속 기간' + ,tr: 'Süre dakika cinsinden' ,zh_cn: '持续时间(分钟)' } ,'Temp Basal' : { @@ -7306,6 +7993,7 @@ function init() { ,ro: 'Bazală temporară' ,sv: 'Temporär basal' ,bg: 'Временен базал' + ,hr: 'Privremeni bazal' ,it: 'Basale Temp' ,nb: 'Midlertidig basal' ,fi: 'Tilapäinen basaali' @@ -7315,6 +8003,7 @@ function init() { ,sk: 'Dočasný bazál' ,nl: 'Tijdelijke basaal' ,ko: '임시 basal' + ,tr: 'Geçici Bazal Oranı' ,zh_cn: '临时基础率' } ,'Temp Basal Start' : { @@ -7327,6 +8016,7 @@ function init() { ,fr: 'Début du débit basal temporaire' ,dk: 'Midlertidig basal start' ,bg: 'Начало на временен базал' + ,hr: 'Početak privremenog bazala' ,it: 'Inizio Basale Temp' ,nb: 'Midlertidig basal start' ,fi: 'Tilapäinen basaali alku' @@ -7336,6 +8026,7 @@ function init() { ,sk: 'Začiatok dočasného bazálu' ,nl: 'Start tijdelijke basaal' ,ko: '임시 basal 시작' + ,tr: 'Geçici Bazal Oranını Başlanğıcı' ,zh_cn: '临时基础率开始' } ,'Temp Basal End' : { @@ -7345,6 +8036,7 @@ function init() { ,fr: 'Fin du débit basal temporaire' ,sv: 'Temporär basalavslut' ,bg: 'Край на временен базал' + ,hr: 'Kraj privremenog bazala' ,it: 'Fine Basale Temp' ,de: 'Ende Temporäre Basalrate' ,es: 'Fin Tasa Basal temporal' @@ -7357,6 +8049,7 @@ function init() { ,sk: 'Koniec dočasného bazálu' ,nl: 'Einde tijdelijke basaal' ,ko: '임시 basal 종료' + ,tr: 'Geçici bazal oranını Bitişi' ,zh_cn: '临时基础率结束' } ,'Percent' : { // value in % for temp basal @@ -7370,6 +8063,7 @@ function init() { ,el: 'Επι τοις εκατό' ,sv: 'Procent' ,bg: 'Процент' + ,hr: 'Postotak' ,it: 'Percentuale' ,nb: 'Prosent' ,fi: 'Prosentti' @@ -7379,6 +8073,7 @@ function init() { ,sk: 'Percent' ,nl: 'Procent' ,ko: '퍼센트' + ,tr: 'Yüzde' ,zh_cn: '百分比' } ,'Basal change in %' : { @@ -7388,6 +8083,7 @@ function init() { ,fr: 'Changement du débit basal en %' ,sv: 'Basaländring i %' ,bg: 'Промяна на базала с %' + ,hr: 'Promjena bazala u %' ,de: 'Basalratenänderung in %' ,es: 'Basal modificado en %' ,dk: 'Basal ændring i %' @@ -7400,6 +8096,7 @@ function init() { ,sk: 'Zmena bazálu v %' ,nl: 'Basaal aanpassing in %' ,ko: '% 이내의 basal 변경' + ,tr: 'Bazal değişimi % cinsinden' ,zh_cn: '基础率变化百分比' } ,'Basal value' : { // absolute value for temp basal @@ -7412,21 +8109,24 @@ function init() { ,es: 'Valor basal' ,dk: 'Basalværdi' ,bg: 'Временен базал' + ,hr: 'Vrijednost bazala' ,it: 'Valore Basale' ,nb: 'Basalverdi' ,fi: 'Basaalin määrä' ,pl: 'Dawka podstawowa' ,pt: 'Valor da basal' - ,ru: 'Временный базал' + ,ru: 'величина временного базалал' ,sk: 'Hodnota bazálu' ,nl: 'Basaal snelheid' ,ko: 'Basal' + ,tr: 'Bazal değeri' ,zh_cn: '基础率值' } ,'Absolute basal value' : { cs: 'Hodnota bazálu' ,he: 'ערך בזלי מוחלט ' ,bg: 'Базална стойност' + ,hr: 'Apsolutna vrijednost bazala' ,it: 'Valore Basale Assoluto' ,fr: 'Débit basal absolu' ,de: 'Absoluter Basalratenwert' @@ -7438,15 +8138,17 @@ function init() { ,fi: 'Absoluuttinen basaalimäärä' ,pl: 'Bezwględna wartość dawki podstawowej' ,pt: 'Valor absoluto da basal' - ,ru: 'Абсолютный базал' + ,ru: 'Абсолютная величина базала' ,sk: 'Absolútna hodnota bazálu' ,nl: 'Absolute basaal waarde' ,ko: '절대적인 basal' + ,tr: 'Mutlak bazal değeri' ,zh_cn: '绝对基础率值' } ,'Announcement' : { cs: 'Oznámení' ,bg: 'Известяване' + ,hr: 'Objava' ,de: 'Ankündigung' ,es: 'Aviso' ,fr: 'Annonce' @@ -7464,6 +8166,7 @@ function init() { ,sk: 'Oznámenia' ,nl: 'Mededeling' ,ko: '공지' + ,tr: 'Duyuru' ,zh_cn: '通告' } ,'Loading temp basal data' : { @@ -7479,12 +8182,14 @@ function init() { ,nb: 'Laster verdier for midlertidig basal' ,fi: 'Lataan tilapäisten basaalien tietoja' ,bg: 'Зареждане на данни за временния базал' + ,hr: 'Učitavanje podataka o privremenom bazalu' ,pl: 'Wczytuje dane tymczasowej dawki podstawowej' ,pt: 'Carregando os dados de basal temporária' ,ru: 'Загрузка данных временного базала' ,sk: 'Nahrávam dáta dočasného bazálu' ,nl: 'Laden tijdelijke basaal gegevens' ,ko: '임시 basal 로딩' + ,tr: 'Geçici bazal verileri yükleniyor' ,zh_cn: '载入临时基础率数据' } ,'Save current record before changing to new?' : { @@ -7497,9 +8202,10 @@ function init() { ,el: 'Αποθήκευση τρεχουσας εγγραφής πριν δημιουργήσουμε νέα?' ,de: 'Aktuelle Einträge speichern?' ,es: 'Grabar datos actuales antes de cambiar por nuevos?' - ,dk: 'Gem aktuelle indgang før der skiftes til ny?' + ,dk: 'Gem aktuelle hændelse før der skiftes til ny?' ,fi: 'Tallenna nykyinen merkintä ennen vaihtoa uuteen?' ,bg: 'Запази текущият запис преди да промениш новия ' + ,hr: 'Spremi trenutni zapis prije promjene na idući?' ,pl: 'Zapisać bieżący rekord przed zamianą na nowy?' ,pt: 'Salvar o registro atual antes de mudar para um novo?' ,ru: 'Сохранить текущие данные перед переходом к новым?' @@ -7507,6 +8213,7 @@ function init() { ,nl: 'Opslaan voor verder te gaan?' ,ko: '새 데이터로 변경하기 전에 현재의 기록을 저장하시겠습니까?' ,it: 'Salvare i dati correnti prima di cambiarli?' + ,tr: 'Yenisine geçmeden önce mevcut girişleri kaydet?' ,zh_cn: '在修改至新值前保存当前记录?' } ,'Profile Switch' : { @@ -7518,10 +8225,11 @@ function init() { ,el: 'Εναλλαγή προφίλ' ,de: 'Profil wechseln' ,es: 'Cambiar Perfil' - ,dk: 'Byt profil' + ,dk: 'Skift profil' ,nb: 'Bytt profil' ,fi: 'Vaihda profiilia' ,bg: 'Смяна на профил' + ,hr: 'Promjena profila' ,pl: 'Przełączenie profilu' ,pt: 'Troca de perfil' ,ru: 'Изменить профиль' @@ -7529,6 +8237,7 @@ function init() { ,nl: 'Profiel wissel' ,ko: '프로파일 변경' ,it: 'Cambio profilo' + ,tr: 'Profil Değiştir' ,zh_cn: '切换配置文件' } ,'Profile' : { @@ -7545,12 +8254,14 @@ function init() { ,nb: 'Profil' ,fi: 'Profiili' ,bg: 'Профил' + ,hr: 'Profil' ,pl: 'Profil' ,pt: 'Perfil' ,ru: 'Профиль' ,sk: 'Profil' ,nl: 'Profiel' ,ko: '프로파일' + ,tr: 'Profil' ,zh_cn: '配置文件' } ,'General profile settings' : { @@ -7566,6 +8277,7 @@ function init() { ,nb: 'Profilinstillinger' ,fi: 'Yleiset profiiliasetukset' ,bg: 'Основни настройки на профила' + ,hr: 'Opće postavke profila' ,pl: 'Ogólne ustawienia profilu' ,pt: 'Configurações de perfil gerais' ,ru: 'Общие настройки профиля' @@ -7573,6 +8285,7 @@ function init() { ,nl: 'Profiel instellingen' ,ko: '일반 프로파일 설정' ,it: 'Impostazioni generali profilo' + ,tr: 'Genel profil ayarları' ,zh_cn: '通用配置文件设置' } ,'Title' : { @@ -7588,6 +8301,7 @@ function init() { ,nb: 'Tittel' ,fi: 'Otsikko' ,bg: 'Заглавие' + ,hr: 'Naslov' ,pl: 'Nazwa' ,pt: 'Título' ,ru: 'Наименование' @@ -7595,6 +8309,7 @@ function init() { ,nl: 'Titel' ,ko: '제목' ,it: 'Titolo' + ,tr: 'Başlık' ,zh_cn: '标题' } ,'Database records' : { @@ -7607,15 +8322,17 @@ function init() { ,nb: 'Databaseverdier' ,de: 'Datenbankeinträge' ,es: 'Registros grabados en base datos' - ,dk: 'Database indgange' + ,dk: 'Database hændelser' ,fi: 'Tietokantamerkintöjä' ,bg: 'Записи в базата с данни' + ,hr: 'Zapisi u bazi' ,pl: 'Rekordy bazy danych' ,pt: 'Registros do banco de dados' ,ru: 'Записи в базе данных' ,sk: 'Záznamy databázi' ,ko: '데이터베이스 기록' ,it: 'Record del database' + ,tr: 'Veritabanı kayıtları' ,zh_cn: '数据库记录' ,nl: 'Database gegevens' } @@ -7629,9 +8346,10 @@ function init() { ,nb: 'Legg til ny rad' ,de: 'Neuen Eintrag hinzufügen' ,es: 'Añadir nuevo registro' - ,dk: 'Tilføj ny indgang' + ,dk: 'Tilføj ny hændelse' ,fi: 'Lisää uusi merkintä' ,bg: 'Добави нов запис' + ,hr: 'Dodaj zapis' ,pl: 'Dodaj nowy rekord' ,pt: 'Adicionar novo registro' ,ru: 'Добавить новую запись' @@ -7639,6 +8357,8 @@ function init() { ,nl: 'Toevoegen' ,ko: '새 기록 추가' ,it: 'Aggiungi un nuovo record' + ,ja: '新しい記録を加える' + ,tr: 'Yeni kayıt ekle' ,zh_cn: '新增记录' } ,'Remove this record' : { @@ -7654,6 +8374,7 @@ function init() { ,fk: 'Fjern denne indgang' ,fi: 'Poista tämä merkintä' ,bg: 'Премахни този запис' + ,hr: 'Obriši ovaj zapis' ,pl: 'Usuń ten rekord' ,pt: 'Remover este registro' ,ru: 'Удалить эту запись' @@ -7661,6 +8382,8 @@ function init() { ,nl: 'Verwijder' ,ko: '이 기록 삭' ,it: 'Rimuovi questo record' + ,ja: 'この記録を除く' + ,tr: 'Bu kaydı kaldır' ,zh_cn: '删除记录' } ,'Clone this record to new' : { @@ -7673,9 +8396,10 @@ function init() { ,es: 'Duplicar este registro como nuevo' ,nb: 'Kopier til ny rad' ,de: 'Diesen Eintrag duplizieren' - ,dk: 'Dupliker denne indgang' + ,dk: 'Kopier denne hændelse til ny' ,fi: 'Kopioi tämä merkintä uudeksi' ,bg: 'Копирай този запис като нов' + ,hr: 'Kloniraj zapis' ,pl: 'Powiel ten rekord na nowy' ,pt: 'Duplicar este registro como novo' ,ru: 'Клонировать эту запись в новый' @@ -7683,6 +8407,7 @@ function init() { ,nl: 'Kopieer deze invoer naar nieuwe' ,ko: '이 기록을 새기록으로 복제하기' ,it: 'Clona questo record in uno nuovo' + ,tr: 'Bu kaydı yeniden kopyala' ,zh_cn: '复制记录' } ,'Record valid from' : { @@ -7694,17 +8419,19 @@ function init() { ,el: 'Ισχύει από' ,de: 'Eintrag gültig ab' ,es: 'Registro válido desde' - ,dk: 'Indgang gyldig fra' + ,dk: 'Hændelse gyldig fra' ,nb: 'Rad gyldig fra' ,fi: 'Merkintä voimassa alkaen' ,bg: 'Записът е валиден от ' + ,hr: 'Zapis vrijedi od' ,pl: 'Rekord ważny od' ,pt: 'Registro válido desde' ,ru: 'Запись действительна от' ,sk: 'Záznam platný od' ,nl: 'Geldig van' - ,ko: '유효 기록' + ,ko: '기록을 시작한 날짜' ,it: 'Record valido da' + ,tr: 'Kayıt itibaren geçerli' ,zh_cn: '有效记录,从' } ,'Stored profiles' : { @@ -7720,13 +8447,15 @@ function init() { ,nb: 'Lagrede profiler' ,fi: 'Tallennetut profiilit' ,bg: 'Запаметени профили' + ,hr: 'Pohranjeni profili' ,pl: 'Zachowane profile' ,pt: 'Perfis guardados' - ,ru: 'Запомненные профили' + ,ru: 'Сохраненные профили' ,sk: 'Uložené profily' ,nl: 'Opgeslagen profielen' ,ko: '저장된 프로파일' ,it: 'Profili salvati' + ,tr: 'Kaydedilmiş profiller' //Kayıtlı profiller,Saklanan profiller ,zh_cn: '配置文件已存储' } ,'Timezone' : { @@ -7742,6 +8471,7 @@ function init() { ,nb: 'Tidssone' ,fi: 'Aikavyöhyke' ,bg: 'Часова зона' + ,hr: 'Vremenska zona' ,pl: 'Strefa czasowa' ,pt: 'Fuso horário' ,ru: 'Часовой пояс' @@ -7749,6 +8479,7 @@ function init() { ,nl: 'Tijdzone' ,ko: '타임존' ,it: 'Fuso orario' + ,tr: 'Saat dilimi' ,zh_cn: '时区' } ,'Duration of Insulin Activity (DIA)' : { @@ -7764,6 +8495,7 @@ function init() { ,dk: 'Varighed af insulin aktivitet (DIA)' ,fi: 'Insuliinin vaikutusaika (DIA)' ,bg: 'Продължителност на инсулиновата активност DIA' + ,hr: 'Trajanje aktivnosti inzulina (DIA)' ,pl: 'Czas trwania aktywnej insuliny (DIA)' ,pt: 'Duração da Atividade da Insulina (DIA)' ,ru: 'Время действия инсулина (DIA)' @@ -7771,6 +8503,7 @@ function init() { ,nl: 'Werkingsduur insuline (DIA)' ,ko: '활성 인슐린 지속 시간(DIA)' ,it: 'Durata Attività Insulinica (DIA)' + ,tr: 'İnsülin Etki Süresi (DIA)' ,zh_cn: '胰岛素作用时间(DIA)' } ,'Represents the typical duration over which insulin takes effect. Varies per patient and per insulin type. Typically 3-4 hours for most pumped insulin and most patients. Sometimes also called insulin lifetime.' : { @@ -7786,6 +8519,7 @@ function init() { ,nb: 'Representerer typisk insulinvarighet. Varierer per pasient og per insulin type. Vanligvis 3-4 timer for de fleste typer insulin og de fleste pasientene. Noen ganger også kalt insulinlevetid.' ,fi: 'Kertoo insuliinin tyypillisen vaikutusajan. Vaihtelee potilaan ja insuliinin tyypin mukaan. Tyypillisesti 3-4 tuntia pumpuissa käytettävällä insuliinilla.' ,bg: 'Представя типичната продължителност на действието на инсулина. Варира между отделните пациенти и различни инсулини. Обикновено е 3-4 часа за пациентите с помпа. Нарича се още живот на инсулина ' + ,hr: 'Predstavlja uobičajeno trajanje djelovanje inzulina. Varira po vrstama inzulina i osobama. Tipično je to 3-4 sata za inzuline u pumpama za većinu osoba. Ponekad se naziva i vijek inzulina' ,pl: 'Odzwierciedla czas działania insuliny. Może różnić się w zależności od chorego i rodzaju insuliny. Zwykle są to 3-4 godziny dla insuliny podawanej pompą u większości chorych. Inna nazwa to czas trwania insuliny.' ,pt: 'Representa a tempo típico durante o qual a insulina tem efeito. Varia de acordo com o paciente e tipo de insulina. Tipicamente 3-4 horas para a maioria das insulinas usadas em bombas e dos pacientes. Algumas vezes chamada de tempo de vida da insulina' ,ru: 'Представляет типичную продолжительность действия инсулина. Зависит от пациента и от типа инсулина. Обычно 3-4 часа для большинства помповых инсулинов и большинства пациентов' @@ -7793,6 +8527,7 @@ function init() { ,nl: 'Geeft de werkingsduur van de insuline in het lichaam aan. Dit verschilt van patient tot patient er per soort insuline, algemeen gemiddelde is 3 tot 4 uur. ' ,ko: '인슐린이 작용하는 지속시간을 나타냅니다. 사람마다 그리고 인슐린 종류에 따라 다르고 일반적으로 3~4시간간 동안 지속되며 인슐린 작용 시간(Insulin lifetime)이라고 불리기도 합니다.' ,it: 'Rappresenta la durata tipica nel quale l\'insulina ha effetto. Varia in base al paziente ed al tipo d\'insulina. Tipicamente 3-4 ore per la maggior parte dei microinfusori e dei pazienti. Chiamata anche durata d\'azione insulinica.' + ,tr: 'İnsülinin etki ettiği tipik süreye karşılık gelir. Hastaya ve insülin tipine göre değişir. Çoğu pompa insülini ve çoğu hasta için genellikle 3-4 saattir. Bazen de insülin etki süresi de denir.' ,zh_cn: '体现典型的胰岛素活性持续时间。 通过百分比和胰岛素类型体现。对于大多数胰岛素和患者来说是3至4个小时。也称为胰岛素生命周期。' } ,'Insulin to carb ratio (I:C)' : { @@ -7805,18 +8540,44 @@ function init() { ,nb: 'IKH forhold' ,de: 'Insulin/Kohlenhydrate-Verhältnis (I:KH)' ,es: 'Relación Insulina/Carbohidratos (I:C)' - ,dk: 'Insulin til kulhydrat forhold (I:C)' + ,dk: 'Insulin til kulhydrat forhold også kaldet Kulhydrat-insulinratio (I:C)' ,fi: 'Insuliiniannoksen hiilihydraattisuhde (I:HH)' ,bg: 'Съотношение инсулин/въглехидратите ICR I:C И:ВХ' + ,hr: 'Omjer UGH:Inzulin (I:C)' ,pl: 'Współczynnik insulina/węglowodany (I:C)' ,pt: 'Relação Insulina-Carboidrato (I:C)' - ,ru: 'Соотношение инсулин/углеводы' + ,ru: 'Соотношение инсулин/углеводы I:C' ,sk: 'Inzulín-sacharidový pomer (I:C)' ,nl: 'Insuline - Koolhydraat ratio (I:C)' - ,ko: '탄수화물에 대한 인슐린 비율(I:C)' + ,ko: '인슐린에 대한 탄수화물 비율(I:C)' ,it: 'Rapporto Insulina-Carboidrati (I:C)' + ,tr: 'İnsülin/Karbonhidrat oranı (I:C)' ,zh_cn: '碳水化合物系数(ICR)' } + ,'Hours:' : { + cs: 'Hodin:' + ,he: 'שעות:' + ,ro: 'Ore:' + ,el: 'ώρες:' + ,fr: 'Heures:' + ,de: 'Stunden:' + ,es: 'Horas:' + ,dk: 'Timer:' + ,sv: 'Timmar:' + ,nb: 'Timer:' + ,fi: 'Tunnit:' + ,bg: 'часове:' + ,hr: 'Sati:' + ,pl: 'Godziny:' + ,pt: 'Horas:' + ,ru: 'час:' + ,sk: 'Hodiny:' + ,nl: 'Uren:' + ,ko: '시간:' + ,it: 'Ore:' + ,tr: 'Saat:' + ,zh_cn: '小时:' + } ,'hours' : { cs: 'hodin' ,he: 'שעות ' @@ -7830,6 +8591,7 @@ function init() { ,nb: 'timer' ,fi: 'tuntia' ,bg: 'часове' + ,hr: 'sati' ,pl: 'godziny' ,pt: 'horas' ,ru: 'час' @@ -7837,6 +8599,7 @@ function init() { ,nl: 'Uren' ,ko: '시간' ,it: 'ore' + ,tr: 'saat' ,zh_cn: '小时' } ,'g/hour' : { @@ -7851,6 +8614,7 @@ function init() { ,dk: 'g/time' ,fi: 'g/tunti' ,bg: 'гр/час' + ,hr: 'g/h' ,pl: 'g/godzine' ,pt: 'g/hora' ,ru: 'г/час' @@ -7858,6 +8622,7 @@ function init() { ,nl: 'g/uur' ,ko: 'g/시간' ,it: 'g/ora' + ,tr: 'g/saat' ,zh_cn: 'g/小时' } ,'g carbs per U insulin. The ratio of how many grams of carbohydrates are offset by each U of insulin.' : { @@ -7867,17 +8632,20 @@ function init() { ,fr: 'g de glucides par Uninté d\'insuline. Le rapport représentant la quantité de glucides compensée par une unité d\'insuline.' ,el: 'Γραμμάρια (g) υδατάνθρακα ανά μονάδα (U) ινσουλίνης. Πόσα γραμμάρια υδατάνθρακα αντιστοιχούν σε μία μονάδα ινσουλίνης' ,sv: 'gram kolhydrater per enhet insulin. Antal gram kolhydrater varje enhet insulin sänker' + ,dk: 'gram kulhydrater per enhed insulin. (Kulhydrat-insulinratio) Den mængde kulhydrat, som dækkes af en enhed insulin.' ,es: 'gr. de carbohidratos por unidad de insulina. La proporción de cuántos gramos de carbohidratos se consumen por unidad de insulina.' ,de: 'g Kohlenhydrate pro Einheit Insulin. Das Verhältnis wie viele Gramm Kohlenhydrate je Einheit Insulin verbraucht werden.' ,nb: 'g karbohydrater per enhet insulin. Beskriver hvor mange gram karbohydrater som hånderes av en enhet insulin.' ,fi: 'g hiilihydraattia / yksikkö insuliinia. Suhde, joka kertoo montako grammaa hiilihydraattia vastaa yhtä yksikköä insuliinia.' ,bg: 'грам въглехидрат към 1 единица инсулин. Съотношението колко грама въглехидрат се покриват от 1 единица инсулин.' + ,hr: 'grama UGH po jedinici inzulina. Omjer koliko grama UGH pokriva jedna jedinica inzulina.' ,pl: 'g węglowodanów na 1j insuliny. Współczynnik ilości gram weglowodanów równoważonych przez 1j insuliny.' ,pt: 'g de carboidrato por unidade de insulina. A razão de quantos gramas de carboidrato são processados por cada unidade de insulina.' - ,ru: 'г углеводов на ед инсулина. Соотношение ед инсулина необходимого для компенсации некоторого количества граммов углеводов' + ,ru: 'г углеводов на ед инсулина. Соотношение показывает количество гр углеводов компенсируемого единицей инсулина.' ,sk: 'gramy sacharidov na jednotku inzulínu. Pomer udáva aké množstvo sacharidov pokryje jednotka inzulínu.' ,ko: '인슐린 단위 당 탄수화물 g. 인슐린 단위에 대한 탄수화물의 양의 비율을 나타냅니다.' ,it: 'g carbo per U di insulina. Il rapporto tra quanti grammi di carboidrati sono compensati da ogni U di insulina.' + ,tr: 'İnsülin ünite başına g karbonhidrat. İnsülin ünite başına kaç gram karbonhidrat tüketildiği oranıdır.' ,zh_cn: '克碳水每单位胰岛素。每单位胰岛素可以抵消的碳水化合物克值比例。' ,nl: 'G KH per Eh insuline. De verhouding tussen hoeveel grammen koohlhydraten er verwerkt kunnen worden per eenheid insuline.' } @@ -7890,16 +8658,19 @@ function init() { ,fr: 'Facteur de sensibilité à l\'insuline (ISF)' ,el: 'Ευαισθησία στην Ινοσυλίνη (ISF)' ,sv: 'Insulinkänslighetsfaktor (ISF)' + ,dk: 'Insulinfølsomhedsfaktor (ISF)' ,nb: 'Insulinfølsomhetsfaktor' ,fi: 'Insuliiniherkkyys (ISF)' ,bg: 'Фактор на инсулинова чувствителност ISF ' + ,hr: 'Faktor inzulinske osjetljivosti (ISF)' ,pl: 'Współczynnik wrażliwosci na insulinę (ISF)' ,pt: 'Fator de Sensibilidade da Insulina (ISF)' - ,ru: 'Фактор чувствительности к инсулину' + ,ru: 'Фактор чувствительности к инсулину ISF' ,sk: 'Citlivosť na inzulín (ISF)' ,nl: 'Insuline gevoeligheid (ISF)' ,ko: '인슐린 민감도(ISF)' ,it: 'Fattore di Sensibilità Insulinica (ISF)' + ,tr: '(ISF) İnsülin Duyarlılık Faktörü' ,zh_cn: '胰岛素敏感系数(ISF)' } ,'mg/dL or mmol/L per U insulin. The ratio of how much BG changes with each U of corrective insulin.' : { @@ -7911,9 +8682,11 @@ function init() { ,de: 'mg/dL oder mmol/L pro Einheit Insulin. Verhältnis von BG-Veränderung je Einheit Korrekturinsulin.' ,es: 'mg/dl o mmol/L por unidad Insulina. La relación de la caída de glucosa y cada unidad de insulina de corrección administrada.' ,sv: 'mg/dl eller mmol per enhet insulin. Hur varje enhet insulin sänker blodsockret' + ,dk: 'mg/dl eller mmol per enhed insulin. Beskriver hvor mange mmol eller mg/dl blodsukkeret sænkes per enhed insulin (Insulinfølsomheden)' ,nb: 'mg/dl eller mmol/l per enhet insulin. Beskriver hvor mye blodsukkeret senkes per enhet insulin.' ,fi: 'mg/dL tai mmol/L / 1 yksikkö insuliinia. Suhde, joka kertoo montako yksikköä verensokeria yksi yksikkö insuliinia laskee.' ,bg: 'мг/дл или ммол към 1 единица инсулин. Съотношението как се променя кръвната захар със всяка единица инсулинова корекция' + ,hr: 'md/dL ili mmol/L po jedinici inzulina. Omjer koliko se GUK mijenja sa jednom jedinicom korekcije inzulina.' ,pl: 'mg/dl lub mmol/L na 1j insuliny. Współczynnik obniżenia poziomu glukozy przez 1j insuliny' ,pt: 'mg/dL ou mmol/L por unidade de insulina. A razão entre queda glicêmica e cada unidade de insulina de correção administrada.' ,ru: 'мг/дл или ммол/л на единицу инсулина. Насколько меняется СК с каждой единицей коррегирующего инсулина' @@ -7921,6 +8694,7 @@ function init() { ,nl: 'mg/dL of mmol/L per E insuline. Ratio daling BG waarde per eenheid correctie' ,ko: '인슐린 단위당 mg/dL 또는 mmol/L. 인슐린 단위당 얼마나 많은 혈당 변화가 있는지의 비율을 나타냅니다.' ,it: 'mg/dL o mmol/L per U insulina. Il rapporto di quanto la glicemia varia per ogni U di correzione insulinica.' + ,tr: 'Ünite insülin başına mg/dL veya mmol/L. Her bir Ünite düzeltme insülin ile KŞ\'nin ne kadar değiştiğini gösteren orandır.' ,zh_cn: 'mg/dL或mmol/L每单位胰岛素。每单位输入胰岛素导致血糖变化的比例' } ,'Carbs activity / absorption rate' : { @@ -7932,9 +8706,11 @@ function init() { ,de: 'Kohlenhydrataktivität / Aufnahme Kohlenhydrate' ,es: 'Actividad de carbohidratos / tasa de absorción' ,sv: 'Kolhydratstid' + ,dk: 'Kulhydrattid / optagelsestid' ,nb: 'Karbohydrattid' ,fi: 'Hiilihydraattiaktiivisuus / imeytymisnopeus' ,bg: 'Активност на въглехидратите / време за абсорбиране' + ,hr: 'UGH aktivnost / omjer apsorpcije' ,pl: 'Aktywność węglowodanów / współczynnik absorpcji' ,pt: 'Atividade dos carboidratos / taxa de absorção' ,ru: 'Активность углеводов / скорость усвоения' @@ -7942,6 +8718,7 @@ function init() { ,nl: 'Koolhydraat opname snelheid' ,ko: '활성 탄수화물/흡수율' ,it: 'Attività carboidrati / Velocità di assorbimento' + ,tr: 'Karbonhidrat aktivitesi / emilim oranı' ,zh_cn: '碳水化合物活性/吸收率' } ,'grams peUr unit time. Represents both the change in COB per unit of time, as well as the amount of carbs that should take effect over that time. Carb absorption / activity curves are less well understood than insulin activity, but can be approximated using an initial delay followed by a constant rate of absorption (g/hr).' : { @@ -7951,17 +8728,20 @@ function init() { ,fr: 'grammes par unité de temps. Représente l\'augmentation de COB par unité de temps et la quantité de glucides agissant durant cette période. L\'absorption des glucides est imprécise et est évaluée en moyenne. L\'unité est grammes par heure (g/h).' ,ro: 'grame pe unitatea de timp. Reprezintă atât schimbarea COB pe unitatea de timp, cât și cantitatea de carbohidrați care ar influența în perioada de timp. Graficele ratei de absorbție sunt mai puțin înțelese decât senzitivitatea la insulină, dar se poate aproxima folosind o întârziere implicită și apoi o rată constantă de aborbție (g/h).' ,sv: 'gram per tidsenhet. Representerar både ändring i aktiva kolhydrater per tidsenhet som mängden kolhydrater som tas upp under denna tid. Kolhydratsupptag / aktivitetskurvor är svårare att förutspå än aktivt insulin men kan beräknas genom att använda en startfördröjning följd av en konstant absorbtionsgrad (g/timme) ' + ,dk: 'gram per tidsenhed. Repræsentere både ændring i aktive kulhydrater per tidsenhed, samt mængden af kulhydrater der indtages i dette tidsrum. Kulhydratsoptag / aktivitetskurver er svære at forudse i forhold til aktiv insulin, men man kan lave en tilnærmet beregning, ved at inkludere en forsinkelse i beregningen, sammen med en konstant absorberingstid (g/time).' ,de: 'Gramm pro Zeiteinheit. Bedeutet sowohl die Änderung in COB je Zeiteinheit, als auch die Menge an Kohlenhydraten die über diese Zeit wirken sollten. Kohlenhydrat-Absorption / Aktivitätskurven werden weniger genau verstanden als Insulinaktivität, aber sie können angenähert werden indem eine Anfangsverzögerung mit konstanter Aufnahme (g/Std.) verwendet wird.' ,es: 'gramos por unidad de tiempo. Representa tanto el cambio en carbohidratos activos por unidad de tiempo como la cantidad de carbohidratos absorbidos durante este tiempo. Las curvas de captación / actividad de carbohidratos son más difíciles de predecir que la insulina activa, pero se pueden calcular utilizando un retraso de inicio seguido de una tasa de absorción constante (gr/h)' ,nb: 'gram per tidsenhet. Representerer både endringen i COB per tidsenhet, såvel som mengden av karbohydrater som blir tatt opp i løpet av den tiden. Carb absorpsjon / virkningskurver er mindre forstått enn insulinaktivitet, men kan tilnærmes ved hjelp av en forsinkelse fulgt av en konstant hastighet av absorpsjon ( g / time ) .' ,fi: 'grammaa / aika. Kertoo tyypillisen nopeuden, jolla hiilihydraatit imeytyvät syömisen jälkeen. Imeytyminen tunnetaan jokseenkin huonosti, mutta voidaan arvioida keskimääräisesti. Yksikkönä grammaa tunnissa (g/h).' ,bg: 'грам за единица време. Представлява както промяната в COB за единица време, така и количеството ВХ които биха се усвоили за това време.' + ,hr: 'grama po jedinici vremena. Predstavlja promjenu aktivnih UGH u jedinici vremena, kao i količinu UGH koja bi trebala utjecati kroz to vrijeme.' ,pl: 'g na jednostkę czasu. Odzwierciedla zmianę COB na jednostkę czasu oraz ilość węglowodanów mających przynieść efekt w czasie. Krzywe absorpcji / aktywnosci węglowodanów są mniej poznane niż aktywności insuliny ale mogą być oszacowane przez ocenę opóźnienia wchłaniania przy stałym współczynniku absorpcji (g/h).' ,pt: 'Gramas por unidade de tempo. Representa a mudança em COB por unidade de tempo, bem como a quantidade de carboidratos que deve ter efeito durante esse período de tempo. Absorção de carboidrato / curvas de atividade são menos conhecidas que atividade de insulina, mas podem ser aproximadas usando um atraso inicial seguido de uma taxa de absorção constante (g/h). ' ,ru: 'грамм на ед времени. Представляет изменение кол-ва углеводов в организме (COB)за единицу времени a также количество активных углеводов' ,sk: 'gramy za jednotku času. Reprezentuje súčasne zmenu COB za jednotku času, ako aj množstvo sacharidov ktoré sa za tú dobu prejavili. Krivka vstrebávania sacharidov je omnoho menej pochopiteľná ako pôsobenie inzulínu (IOB), ale môže byť približne s použitím počiatočného oneskorenia a následne s konštantným vstrebávaním (g/hod). ' ,ko: '단위 시간당 그램. 시간당 작용하는 탄수화물의 총량 뿐 아니라 시간 단위당 COB의 변화를 나타냅니다. 탄수화물 흡수율/활성도 곡선은 인슐린 활성도보다 이해가 잘 되지는 않지만 지속적인 흡수율(g/hr)에 따른 초기 지연을 사용하여 근사치를 구할 수 있습니다.' ,it: 'grammi per unità di tempo. Rappresentano sia il cambio di COB per unità di tempo, sia la quantità di carboidrati che faranno effetto nel tempo. Assorbimento di carboidrati / curva di attività sono meno conosciute rispetto all\'attività insulinica, ma possono essere approssimate usando un ritardo iniziale seguito da un rapporto costante di assorbimento (g/hr).' + ,tr: 'Ur birim zaman başına gram. Birim zamanda (COB) Aktif Krabonhidratdaki değişimin yanı sıra o zaman üzerinde etki etmesi gereken karbonhidrat miktarını ifade eder. Karbonhidrat emme/aktivite eğrileri, insülin aktivitesinden daha zor anlaşılmaktadır, ancak bir başlangıç gecikmesi ve ardından sabit bir emilim oranı (g/hr) kullanılarak yaklaşık olarak tahmin edilebilmektedir.' ,zh_cn: '克每单位时间。表示每单位时间COB(活性碳水化合物)的变化,以及在该时间应该生效的碳水化合物的量。碳水化合物活性/吸收曲线比胰岛素活性难理解,但可以使用初始延迟,接着恒定吸收速率(克/小时)来近似模拟。' ,nl: 'grammen per tijdseenheid. Geeft de wijzigingen in COB per tijdseenheid alsookde hoeveelheid KH dat impact zou moeten hebben over deze tijdspanne. KH absorbtie / activiteits curveszijn minder eenvoudig uitzetbaar, maar kunnen geschat worden door gebruik te maken van een startvertreging en daarna een constante curve van absorbtie (g/u).' } @@ -7978,13 +8758,15 @@ function init() { ,nb: 'Basal [enhet/t]' ,fi: 'Basaali [yksikköä/tunti]' ,bg: 'Базална стойност [единица/час]' + ,hr: 'Bazali [jedinica/sat]' ,pl: 'Dawka podstawowa [j/h]' ,pt: 'Taxas de basal [unidades/hora]' - ,ru: 'Стойкость базала ед/час' + ,ru: 'Базал ед/час' ,sk: 'Bazál [U/hod]' ,nl: 'Basaal snelheid [eenheden/uur]' ,ko: 'Basal 비율[unit/hour]' ,it: 'Basale [unità/ora]' + ,tr: 'Bazal oranı [ünite/saat]' ,zh_cn: '基础率 [U/小时]' } ,'Target BG range [mg/dL,mmol/L]' : { @@ -7993,13 +8775,14 @@ function init() { ,ro: 'Intervalul țintă al glicemiei [mg/dL, mmol/L]' ,el: 'Στόχος Γλυκόζης Αίματος [mg/dl , mmol/l]' ,de: 'Blutzucker-Zielbereich [mg/dL, mmol/L]' - ,dk: 'Ønskbart blodsukkerinterval [mg/dl,mmol]' + ,dk: 'Ønsket blodsukkerinterval [mg/dl,mmol]' ,es: 'Intervalo glucemia dentro del objetivo [mg/dL,mmol/L]' ,fr: 'Cible d\'intervalle de glycémie' ,sv: 'Önskvärt blodsockerintervall [mg/dl,mmol]' ,nb: 'Ønsket blodsukkerintervall [mg/dl,mmmol/l]' ,fi: 'Tavoitealue [mg/dL tai mmol/L]' ,bg: 'Целеви диапазон на КЗ [мг/дл , ммол]' + ,hr: 'Ciljani raspon GUK [mg/dL,mmol/L]' ,pl: 'Docelowy przedział glikemii [mg/dl, mmol/L])' ,pt: 'Meta de glicemia [mg/dL, mmol/L]' ,ru: 'Целевой диапазон СК [mg/dL,mmol/L]' @@ -8007,6 +8790,7 @@ function init() { ,nl: 'Doel BG waarde in [mg/dl,mmol/L' ,ko: '목표 혈당 범위 [mg/dL,mmol/L]' ,it: 'Obiettivo d\'intervallo glicemico [mg/dL,mmol/L]' + ,tr: 'Hedef KŞ aralığı [mg / dL, mmol / L]' ,zh_cn: '目标血糖范围 [mg/dL,mmol/L]' } ,'Start of record validity' : { @@ -8017,18 +8801,20 @@ function init() { ,el: 'Ισχύει από' ,sv: 'Starttid för händelse' ,de: 'Beginn der Aufzeichnungsgültigkeit' - ,dk: 'Starttid for hændelse' + ,dk: 'Starttid for gyldighed' ,es: 'Inicio de validez de datos' ,nb: 'Starttidspunkt for gyldighet' ,fi: 'Merkinnän alkupäivämäärä' ,bg: 'Начало на записа' + ,hr: 'Trenutak važenja zapisa' ,pl: 'Początek ważnych rekordów' ,pt: 'Início da validade dos dados' - ,ru: 'Начало записей' + ,ru: 'Начало валидности записей' ,sk: 'Začiatok platnosti záznamu' ,nl: 'Start geldigheid' ,ko: '기록 유효기간의 시작일' ,it: 'Inizio di validità del dato' + ,tr: 'Kayıt geçerliliği başlangıcı' ,zh_cn: '有效记录开始' } ,'Icicle' : { @@ -8044,12 +8830,14 @@ function init() { ,nb: 'Isfjell' ,fi: 'Jääpuikko' ,bg: 'Висящ' + ,hr: 'Padajuće' ,pl: 'Odwrotność' ,pt: 'Inverso' ,nl: 'Ijspegel' ,ru: 'Силуэт сосульки' ,sk: 'Inverzne' ,ko: '고드름 방향' + ,tr: 'Buzsaçağı' //Sarkıt ,zh_cn: 'Icicle' ,zh_tw: 'Icicle' } @@ -8067,12 +8855,14 @@ function init() { ,nb: 'Basalgraf' ,fi: 'Näytä basaali' ,bg: 'Базал' + ,hr: 'Iscrtaj bazale' ,pl: 'Zmiana dawki bazowej' ,pt: 'Renderizar basal' - ,ru: 'Дать базал' + ,ru: 'отрисовать базал' ,sk: 'Zobrazenie bazálu' ,nl: 'Toon basaal' ,ko: 'Basal 사용하기' + ,tr: 'Bazal Grafik' ,zh_cn: '使用基础率' ,zh_tw: '使用基礎率' } @@ -8089,6 +8879,7 @@ function init() { ,nb: 'Brukt profil' ,fi: 'Käytetty profiili' ,bg: 'Използван профил' + ,hr: 'Korišteni profil' ,pl: 'Profil wykorzystywany' ,pt: 'Perfil utilizado' ,ru: 'Используемый профиль' @@ -8096,6 +8887,7 @@ function init() { ,nl: 'Gebruikt profiel' ,ko: '프로파일이 사용됨' ,it: 'Profilo usato' + ,tr: 'Kullanılan profil' ,zh_cn: '配置文件已使用' } ,'Calculation is in target range.' : { @@ -8105,19 +8897,21 @@ function init() { ,fr: 'La valeur calculée est dans l\'intervalle cible' ,el: 'Ο υπολογισμός είναι εντός στόχου' ,de: 'Berechnung ist innerhalb des Zielbereichs' - ,dk: 'Inden i intervalområde' + ,dk: 'Beregning er i målområdet' ,es: 'El cálculo está dentro del rango objetivo' ,sv: 'Inom intervallområde' ,nb: 'Innenfor målområde' ,fi: 'Laskettu arvo on tavoitealueella' ,bg: 'Калкулацията е в граници' + ,hr: 'Izračun je u ciljanom rasponu.' ,pl: 'Obliczenie mieści się w zakresie docelowym' ,pt: 'O cálculo está dentro da meta' - ,ru: 'Расчет в целевых пределах ' + ,ru: 'Расчет в целевом диапазоне ' ,sk: 'Výpočet je v cieľovom rozsahu.' ,nl: 'Berekening valt binnen doelwaards' ,ko: '계산은 목표 범위 안에 있습니다.' ,it: 'Calcolo all\'interno dell\'intervallo' + ,tr: 'Hesaplama hedef aralıktadır.' ,zh_cn: '预计在目标范围内' } ,'Loading profile records ...' : { @@ -8133,6 +8927,7 @@ function init() { ,sv: 'Laddar profildata ...' ,fi: 'Ladataan profiileja ...' ,bg: 'Зареждане на профили' + ,hr: 'Učitavanje profila...' ,pl: 'Wczytywanie rekordów profilu' ,pt: 'Carregando dados do perfil ...' ,ru: 'Загрузка записей профиля' @@ -8140,6 +8935,7 @@ function init() { ,nl: 'Laden profiel gegevens' ,ko: '프로파일 기록 로딩' ,it: 'Caricamento dati del profilo ...' + ,tr: 'Profil kayıtları yükleniyor ...' ,zh_cn: '载入配置文件记录...' } ,'Values loaded.' : { @@ -8155,6 +8951,7 @@ function init() { ,sv: 'Värden laddas' ,fi: 'Arvot ladattu' ,bg: 'Стойностите за заредени.' + ,hr: 'Vrijednosti učitane.' ,pl: 'Wartości wczytane.' ,pt: 'Valores carregados.' ,ru: 'Данные загружены' @@ -8162,6 +8959,7 @@ function init() { ,nl: 'Gegevens geladen' ,ko: '값이 로드됨' ,it: 'Valori caricati.' + ,tr: 'Değerler yüklendi.' ,zh_cn: '已载入数值' } ,'Default values used.' : { @@ -8177,6 +8975,7 @@ function init() { ,sv: 'Standardvärden valda' ,fi: 'Oletusarvot ladattu' ,bg: 'Стойностите по подразбиране са използвани.' + ,hr: 'Koriste se zadane vrijednosti.' ,pl: 'Używane domyślne wartości.' ,pt: 'Valores padrão em uso.' ,ru: 'Используются значения по умолчанию' @@ -8184,6 +8983,7 @@ function init() { ,nl: 'Standaard waardes gebruikt' ,ko: '초기 설정 값이 사용됨' ,it: 'Valori standard usati.' + ,tr: 'Varsayılan değerler kullanıldı.' ,zh_cn: '已使用默认值' } ,'Error. Default values used.' : { @@ -8199,6 +8999,7 @@ function init() { ,sv: 'Error. Standardvärden valda.' ,fi: 'Virhe! Käytetään oletusarvoja.' ,bg: 'Грешка. Стойностите по подразбиране са използвани.' + ,hr: 'Pogreška. Koristiti će se zadane vrijednosti.' ,pl: 'Błąd. Używane domyślne wartości.' ,pt: 'Erro. Valores padrão em uso.' ,ru: 'Ошибка. Используются значения по умолчанию' @@ -8206,6 +9007,7 @@ function init() { ,nl: 'FOUT: Standaard waardes gebruikt' ,ko: '에러. 초기 설정 값이 사용됨' ,it: 'Errore. Valori standard usati.' + ,tr: 'Hata. Varsayılan değerler kullanıldı.' ,zh_cn: '错误,已使用默认值' } ,'Time ranges of target_low and target_high don\'t match. Values are restored to defaults.' : { @@ -8215,12 +9017,13 @@ function init() { ,fr: 'Les intervalles de temps pour la cible glycémique supérieure et inférieure diffèrent. Les valeurs par défault sont restaurées.' ,el: 'Το χρονικό διάστημα του χαμηλού ορίου/στόχου και του υψηλού, δεν συμπίπτουν. Γίνεται επαναφορά στις προκαθορισμένες τιμές.' ,de: 'Zeitspanne vom untersten und obersten Wert wird nicht berücksichtigt. Werte auf Standard zurückgesetzt.' - ,dk: 'Tidsinterval for målområde for lav og høj stemmer ikke overens' + ,dk: 'Tidsinterval for målområde lav og høj stemmer ikke overens. Standardværdier er brugt' ,es: 'Los marcos temporales para objetivo inferior y objetivo superior no coinciden. Los valores predeterminados son usados.' ,ro: 'Intervalele temporale pentru țintă_inferioară și țintă_superioară nu se potrivesc. Se folosesc valorile implicite.' ,sv: 'Tidsintervall för målområde låg och hög stämmer ej' ,fi: 'Matalan ja korkean tavoitteen aikarajat eivät täsmää. Arvot on vaihdettu oletuksiin.' ,bg: 'Времевите интервали за долна граница на кз и горна граница на кз не съвпадат. Стойностите са възстановени по подразбиране.' + ,hr: 'Vremenski rasponi donje ciljane i gornje ciljane vrijednosti nisu ispravni. Vrijednosti vraćene na zadano.' ,pl: 'Zakres czasu w docelowo niskim i wysokim przedziale nie są dopasowane. Przywrócono wartości domyślne' ,pt: 'Os intervalos de tempo da meta inferior e da meta superior não conferem. Os valores padrão serão restaurados.' ,ru: 'Диапазон времени нижних и верхних целевых значений не совпадают. Восстановлены значения по умолчанию' @@ -8228,6 +9031,7 @@ function init() { ,ko: '설정한 저혈당과 고혈당의 시간 범위와 일치하지 않습니다. 값은 초기 설정값으로 다시 저장 될 것입니다.' ,it: 'Intervalli di tempo della glicemia obiettivo inferiore e superiore non corretti. Valori ripristinati a quelli standard.' ,nl: 'Tijdspanne van laag en hoog doel zijn niet correct. Standaard waarden worden gebruikt' + ,tr: 'Target_low ve target_high öğelerinin zaman aralıkları eşleşmiyor. Değerler varsayılanlara geri yüklendi.' ,zh_cn: '时间范围内的目标高低血糖值不匹配。已恢复使用默认值。' } ,'Valid from:' : { @@ -8243,13 +9047,15 @@ function init() { ,sv: 'Giltig från:' ,fi: 'Alkaen:' ,bg: 'Валиден от' + ,hr: 'Vrijedi od:' ,pl: 'Ważne od:' ,pt: 'Válido desde:' - ,ru: 'Действует с' + ,ru: 'Действительно с' ,sk: 'Platné od:' ,nl: 'Geldig van:' ,ko: '유효' ,it: 'Valido da:' + ,tr: 'Tarihinden itibaren geçerli' ,zh_cn: '生效从:' } ,'Save current record before switching to new?' : { @@ -8265,6 +9071,7 @@ function init() { ,sv: 'Spara före byte till nytt?' ,fi: 'Tallenna nykyinen merkintä ennen vaihtamista uuteen?' ,bg: 'Запазване текущият запис преди превключване на нов?' + ,hr: 'Spremi trenutni zapis prije prelaska na novi?' ,pl: 'Nagrać bieżący rekord przed przełączeniem na nowy?' ,pt: 'Salvar os dados atuais antes de mudar para um novo?' ,ru: 'Сохранить текущие записи перед переходом к новым?' @@ -8272,6 +9079,7 @@ function init() { ,nl: 'Opslaan voor verder te gaan?' ,ko: '새 데이터로 변환하기 전에 현재의 기록을 저장하겠습니까?' ,it: 'Salvare il dato corrente prima di passare ad uno nuovo?' + ,tr: 'Yenisine geçmeden önce mevcut kaydı kaydet' ,zh_cn: '切换至新记录前保存当前记录?' } ,'Add new interval before' : { @@ -8287,6 +9095,7 @@ function init() { ,sv: 'Lägg till nytt intervall före' ,fi: 'Lisää uusi aikaväli ennen' ,bg: 'Добави интервал преди' + ,hr: 'Dodaj novi interval iznad' ,pl: 'Dodaj nowy przedział przed' ,pt: 'Adicionar novo intervalo antes de' ,ru: 'Добавить интервал перед' @@ -8294,6 +9103,7 @@ function init() { ,nl: 'Voeg interval toe voor' ,ko: '새로운 구간을 추가하세요' ,it: 'Aggiungere prima un nuovo intervallo' + ,tr: 'Daha önce yeni aralık ekle' ,zh_cn: '在此前新增区间' } ,'Delete interval' : { @@ -8303,12 +9113,13 @@ function init() { ,fr: 'Effacer l\'intervalle' ,el: 'Διαγραφή διαστήματος' ,de: 'Intervall löschen' - ,dk: 'Fjern interval' + ,dk: 'Slet interval' ,es: 'Borrar intervalo' ,ro: 'Șterge interval' ,sv: 'Ta bort intervall' ,fi: 'Poista aikaväli' ,bg: 'Изтрий интервал' + ,hr: 'Obriši interval' ,pl: 'Usuń przedział' ,pt: 'Apagar intervalo' ,ru: 'Удалить интервал' @@ -8316,6 +9127,7 @@ function init() { ,nl: 'Verwijder interval' ,ko: '구간을 지우세요.' ,it: 'Elimina intervallo' + ,tr: 'Aralığı sil' ,zh_cn: '删除区间' } ,'I:C' : { @@ -8330,6 +9142,7 @@ function init() { ,sv: 'I:C' ,fi: 'I:HH' ,bg: 'И:ВХ' + ,hr: 'I:C' ,pl: 'I:C' ,pt: 'I:C' ,ru: 'Инс:Углев' @@ -8337,6 +9150,7 @@ function init() { ,nl: 'I:C' ,ko: 'I:C' ,it: 'I:C' + ,tr: 'İ:K' ,zh_cn: 'ICR' } ,'ISF' : { @@ -8351,13 +9165,15 @@ function init() { ,sv: 'ISF' ,fi: 'ISF' ,bg: 'Инсулинова чувствителност' + ,hr: 'ISF' ,pl: 'ISF' ,pt: 'ISF' ,nl: 'ISF' - ,ru: 'Чувствительность к инсулину' + ,ru: 'Чувствительность к инсулину ISF' ,sk: 'ISF' ,ko: 'ISF' ,it: 'ISF' + ,tr: 'ISF' ,zh_cn: 'ISF' } ,'Combo Bolus' : { @@ -8365,7 +9181,7 @@ function init() { ,he: 'בולוס קומבו ' ,pl: 'Bolus złożony' ,de: 'Verzögerter Bolus' - ,dk: 'Kombo-bolus' + ,dk: 'Kombineret bolus' ,es: 'Combo-Bolo' ,fr: 'Bolus Duo/Combo' ,el: '' @@ -8373,6 +9189,7 @@ function init() { ,sv: 'Combo-bolus' ,nb: 'Kombinasjonsbolus' ,bg: 'Двоен болус' + ,hr: 'Dual bolus' ,fi: 'Yhdistelmäbolus' ,pt: 'Bolus duplo' ,ru: 'Комбинированный болюс' @@ -8380,6 +9197,7 @@ function init() { ,nl: 'Pizza Bolus' ,ko: 'Combo Bolus' ,it: 'Combo Bolo' + ,tr: 'Kombo (Yayma) Bolus' ,zh_cn: '双波' } ,'Difference' : { @@ -8394,6 +9212,7 @@ function init() { ,sv: 'Skillnad' ,nb: 'Forskjell' ,bg: 'Разлика' + ,hr: 'Razlika' ,fi: 'Ero' ,ru: 'Разность' ,sk: 'Rozdiel' @@ -8402,6 +9221,7 @@ function init() { ,nl: 'Verschil' ,ko: '차이' ,it: 'Differenza' + ,tr: 'fark' ,zh_cn: '差别' } ,'New time' : { @@ -8416,6 +9236,7 @@ function init() { ,sv: 'Ny tid' ,nb: 'Ny tid' ,bg: 'Ново време' + ,hr: 'Novo vrijeme' ,fi: 'Uusi aika' ,ru: 'Новое время' ,sk: 'Nový čas' @@ -8424,6 +9245,7 @@ function init() { ,nl: 'Nieuwe tijd' ,ko: '새로운 시간' ,it: 'Nuovo Orario' + ,tr: 'Yeni zaman' ,zh_cn: '新时间' } ,'Edit Mode' : { @@ -8432,12 +9254,13 @@ function init() { ,ro: 'Mod editare' ,fr: 'Mode Édition' ,de: 'Bearbeitungsmodus' - ,dk: 'Redigerings mode' + ,dk: 'Redigerings tilstand' ,es: 'Modo edición' ,sv: 'Editeringsläge' ,el: 'Λειτουργία Επεξεργασίας' ,nb: 'Editeringsmodus' ,bg: 'Редактиране' + ,hr: 'Uređivanje' ,fi: 'Muokkausmoodi' ,ru: 'Режим редактирования' ,sk: 'Editačný mód' @@ -8445,7 +9268,9 @@ function init() { ,pt: 'Modo de edição' ,ko: '편집 모드' ,it: 'Modalità di Modifica' + ,ja: '編集モード' ,nl: 'Wijzigen uitvoeren' + ,tr: 'Düzenleme Modu' ,zh_cn: '编辑模式' ,zh_tw: '編輯模式' } @@ -8461,14 +9286,16 @@ function init() { ,sv: 'Ikon visas när editeringsläge är aktivt' ,nb: 'Ikon vises når editeringsmodus er aktivert' ,bg: 'Когато е активно ,иконката за редактиране ще се вижда' + ,hr: 'Kada je omogućeno, mod uređivanje je omogućen' ,fi: 'Muokkausmoodin ikoni tulee näkyviin kun laitat tämän päälle' - ,ru: 'При активации видна икона начать режим редактирования' + ,ru: 'При активации видна пиктограмма начать режим редактирования' ,sk: 'Keď je povolený, je zobrazená ikona editačného módu' ,pl: 'Po aktywacji, widoczne ikony, aby uruchomić tryb edycji' ,pt: 'Quando ativado, o ícone iniciar modo de edição estará visível' ,nl: 'Als geactiveerd is Wijzigen modus beschikbaar' ,ko: '편집모드를 시작하기 위해 아이콘을 활성화하면 볼 수 있습니다.' ,it: 'Quando abilitata, l\'icona della Modalità di Modifica è visibile' + ,tr: 'Etkinleştirildiğinde düzenleme modunun başında simgesi görünecektir.' ,zh_cn: '启用后开始编辑模式图标可见' ,zh_tw: '啟用後開始編輯模式圖標可見' } @@ -8484,6 +9311,7 @@ function init() { ,sv: 'Operation' ,nb: 'Operasjon' ,bg: 'Операция' + ,hr: 'Operacija' ,fi: 'Operaatio' ,ru: 'Операция' ,sk: 'Operácia' @@ -8492,6 +9320,7 @@ function init() { ,nl: 'Operatie' ,ko: '동작' ,it: 'Operazione' + ,tr: 'İşlem' //Operasyon ,zh_cn: '操作' } ,'Move' : { @@ -8506,6 +9335,7 @@ function init() { ,sv: 'Flytta' ,nb: 'Flytt' ,bg: 'Премести' + ,hr: 'Pomakni' ,fi: 'Liikuta' ,ru: 'Переместить' ,sk: 'Presunúť' @@ -8514,6 +9344,7 @@ function init() { ,nl: 'Verplaats' ,ko: '이동' ,it: 'Muovi' + ,tr: 'Taşı' ,zh_cn: '移动' } ,'Delete' : { @@ -8528,6 +9359,7 @@ function init() { ,sv: 'Ta bort' ,nb: 'Slett' ,bg: 'Изтрий' + ,hr: 'Obriši' ,fi: 'Poista' ,sk: 'Zmazať' ,ru: 'Удалить' @@ -8536,6 +9368,8 @@ function init() { ,nl: 'Verwijder' ,ko: '삭제' ,it: 'Elimina' + ,ja: '削除' + ,tr: 'Sil' ,zh_cn: '删除' } ,'Move insulin' : { @@ -8550,6 +9384,7 @@ function init() { ,sv: 'Flytta insulin' ,nb: 'Flytt insulin' ,bg: 'Премести инсулин' + ,hr: 'Premjesti inzulin' ,fi: 'Liikuta insuliinia' ,ru: 'Переместить инсулин' ,sk: 'Presunúť inzulín' @@ -8558,6 +9393,7 @@ function init() { ,nl: 'Verplaats insuline' ,ko: '인슐린을 이동하세요.' ,it: 'Muovi Insulina' + ,tr: 'İnsülini taşı' ,zh_cn: '移动胰岛素' } ,'Move carbs' : { @@ -8572,6 +9408,7 @@ function init() { ,sv: 'Flytta kolhydrater' ,nb: 'Flytt karbohydrater' ,bg: 'Премести ВХ' + ,hr: 'Premejsti UGH' ,fi: 'Liikuta hiilihydraatteja' ,ru: 'Переместить углеводы' ,sk: 'Presunúť sacharidy' @@ -8580,6 +9417,7 @@ function init() { ,nl: 'Verplaats KH' ,ko: '탄수화물을 이동하세요.' ,it: 'Muovi carboidrati' + ,tr: 'Karbonhidratları taşı' ,zh_cn: '移动碳水' } ,'Remove insulin' : { @@ -8594,6 +9432,7 @@ function init() { ,sv: 'Ta bort insulin' ,nb: 'Fjern insulin' ,bg: 'Изтрий инсулин' + ,hr: 'Obriši inzulin' ,fi: 'Poista insuliini' ,ru: 'Удалить инсулин' ,sk: 'Odstrániť inzulín' @@ -8602,6 +9441,7 @@ function init() { ,nl: 'Verwijder insuline' ,ko: '인슐린을 지우세요.' ,it: 'Rimuovi insulina' + ,tr: 'İnsülini kaldır' ,zh_cn: '去除胰岛素' } ,'Remove carbs' : { @@ -8616,6 +9456,7 @@ function init() { ,sv: 'Ta bort kolhydrater' ,nb: 'Fjern karbohydrater' ,bg: 'Изтрий ВХ' + ,hr: 'Obriši UGH' ,fi: 'Poista hiilihydraatit' ,ru: 'Удалить углеводы' ,sk: 'Odstrániť sacharidy' @@ -8624,6 +9465,7 @@ function init() { ,nl: 'Verwijder KH' ,ko: '탄수화물을 지우세요.' ,it: 'Rimuovi carboidrati' + ,tr: 'Karbonhidratları kaldır' ,zh_cn: '去除碳水' } ,'Change treatment time to %1 ?' : { @@ -8638,14 +9480,16 @@ function init() { ,sv: 'Ändra behandlingstid till %1 ?' ,nb: 'Endre behandlingstid til %1 ?' ,bg: 'Да променя ли времето на събитието с %1?' + ,hr: 'Promijeni vrijeme tretmana na %1?' ,fi: 'Muuta hoidon aika? Uusi: %1' - ,ru: 'Изменить время события на %1?' + ,ru: 'Изменить время события на %1 ?' ,sk: 'Zmeniť čas ošetrenia na %1 ?' ,pl: 'Zmień czas zdarzenia na %1 ?' ,pt: 'Alterar horário do tratamento para %1 ?' ,nl: 'Wijzig behandel tijdstip naar %1' ,ko: '%1 treatment을 변경하세요.' ,it: 'Cambiare tempo alla somministrazione a %1 ?' + ,tr: 'Tedavi tarihini %1 e değiştirilsin mi?' ,zh_cn: '修改操作时间到%1?' } ,'Change carbs time to %1 ?' : { @@ -8660,14 +9504,16 @@ function init() { ,sv: 'Ändra kolhydratstid till %1 ?' ,nb: 'Endre Karbohydrattid til %1 ?' ,bg: 'Да променя ли времето на ВХ с %1?' + ,hr: 'Promijeni vrijeme UGH na %1?' ,fi: 'Muuta hiilihydraattien aika? Uusi: %1' - ,ru: 'Изменить время подачи углеводов на %?' + ,ru: 'Изменить время подачи углеводов на % ?' ,sk: 'Zmeniť čas sacharidov na %1 ?' ,pl: 'Zmień czas węglowodanów na %1 ?' ,pt: 'Alterar horário do carboidrato para %1 ?' ,nl: 'Wijzig KH tijdstip naar %1' ,ko: '%1로 탄수화물 시간을 변경하세요.' ,it: 'Cambiare durata carboidrati a %1 ?' + ,tr: 'Karbonhidrat zamanını %1 e değiştirilsin mi?' ,zh_cn: '修改碳水时间到%1?' } ,'Change insulin time to %1 ?' : { @@ -8682,14 +9528,16 @@ function init() { ,sv: 'Ändra insulintid till %1 ?' ,nb: 'Endre insulintid til %1 ?' ,bg: 'Да променя ли времето на инсулина с %1?' + ,hr: 'Promijeni vrijeme inzulina na %1?' ,fi: 'Muuta insuliinin aika? Uusi: %1' - ,ru: 'Изменить время подачи инсулина на %?' + ,ru: 'Изменить время подачи инсулина на % ?' ,sk: 'Zmeniť čas inzulínu na %1 ?' ,pl: 'Zmień czas insuliny na %1 ?' ,pt: 'Alterar horário da insulina para %1 ?' ,nl: 'Wijzig insuline tijdstip naar %1' ,ko: '%1로 인슐린 시간을 변경하세요.' ,it: 'Cambiare durata insulina a %1 ?' + ,tr: 'İnsülin tarihini %1 e değiştirilsin mi?' // zamanı ,zh_cn: '修改胰岛素时间到%1?' } ,'Remove treatment ?' : { @@ -8704,14 +9552,16 @@ function init() { ,sv: 'Ta bort behandling ?' ,nb: 'Fjern behandling ?' ,bg: 'Изтрий събитието' + ,hr: 'Obriši tretman?' ,fi: 'Poista hoito?' - ,ru: 'Удалить событие?' + ,ru: 'Удалить событие ?' ,sk: 'Odstrániť ošetrenie?' ,pl: 'Usunąć wydarzenie?' ,pt: 'Remover tratamento?' ,nl: 'Verwijder behandeling' ,ko: 'Treatment를 지우세요.' ,it: 'Rimuovere somministrazione ?' + ,tr: 'Tedavi kaldırılsın mı? ' ,zh_cn: '去除操作?' } ,'Remove insulin from treatment ?' : { @@ -8726,14 +9576,16 @@ function init() { ,sv: 'Ta bort insulin från behandling ?' ,nb: 'Fjern insulin fra behandling ?' ,bg: 'Да изтрия ли инсулина от събитието?' + ,hr: 'Obriši inzulin iz tretmana?' ,fi: 'Poista insuliini hoidosta?' - ,ru: 'Удалить инсулин из событий?' + ,ru: 'Удалить инсулин из событий ?' ,sk: 'Odstrániť inzulín z ošetrenia?' ,pl: 'Usunąć insulinę z wydarzenia?' ,pt: 'Remover insulina do tratamento?' ,nl: 'Verwijder insuline van behandeling' ,ko: 'Treatment에서 인슐린을 지우세요.' ,it: 'Rimuovere insulina dalla somministrazione ?' + ,tr: 'İnsülini tedaviden çıkartılsın mı?' ,zh_cn: '从操作中去除胰岛素?' } ,'Remove carbs from treatment ?' : { @@ -8748,14 +9600,16 @@ function init() { ,sv: 'Ta bort kolhydrater från behandling ?' ,nb: 'Fjern karbohydrater fra behandling ?' ,bg: 'Да изтрия ли ВХ от събитието?' + ,hr: 'Obriši UGH iz tretmana?' ,fi: 'Poista hiilihydraatit hoidosta?' - ,ru: 'Удалить углеводы из событий?' + ,ru: 'Удалить углеводы из событий ?' ,sk: 'Odstrániť sacharidy z ošetrenia?' ,pl: 'Usunąć węglowodany z wydarzenia?' ,pt: 'Remover carboidratos do tratamento?' ,nl: 'Verwijder KH van behandeling' ,ko: 'Treatment에서 탄수화물을 지우세요.' ,it: 'Rimuovere carboidrati dalla somministrazione ?' + ,tr: 'Karbonhidratları tedaviden çıkartılsın mı ?' // kaldırılsın mı ,zh_cn: '从操作中去除碳水化合物?' } ,'Rendering' : { @@ -8770,6 +9624,7 @@ function init() { ,sv: 'Rendering' ,nb: 'Rendering' ,bg: 'Показване на графика' + ,hr: 'Iscrtavanje' ,fi: 'Piirrän graafeja' ,ru: 'Построение графика' ,sk: 'Vykresľujem' @@ -8778,6 +9633,7 @@ function init() { ,nl: 'Renderen' ,ko: '랜더링' ,it: 'Traduzione' + ,tr: 'Grafik oluşturuluyor...' ,zh_cn: '渲染' } ,'Loading OpenAPS data of' : { @@ -8792,14 +9648,16 @@ function init() { ,sv: 'Laddar OpenAPS data för' ,nb: 'Laster OpenAPS data for' ,bg: 'Зареждане на OpenAPS данни от' + ,hr: 'Učitavanje OpenAPS podataka od' ,fi: 'Lataan OpenAPS tietoja' - ,ru: 'Загрузка данных OpenAPS' + ,ru: 'Загрузка данных OpenAPS от' ,sk: 'Nahrávam OpenAPS dáta z' ,pl: 'Ładuję dane OpenAPS z' ,pt: 'Carregando dados de OpenAPS de' ,ko: 'OpenAPS 데이터 로딩' ,it: 'Caricamento in corso dati OpenAPS' ,nl: 'OpenAPS gegevens opladen van' + ,tr: 'dan OpenAPS verileri yükleniyor' ,zh_cn: '载入OpenAPS数据从' } ,'Loading profile switch data' : { @@ -8814,6 +9672,7 @@ function init() { ,sv: 'Laddar ny profildata' ,nb: 'Laster nye profildata' ,bg: 'Зареждане на данни от сменения профил' + ,hr: 'Učitavanje podataka promjene profila' ,fi: 'Lataan profiilinvaihtotietoja' ,ru: 'Загрузка данных нового профиля' ,sk: 'Nahrávam dáta prepnutia profilu' @@ -8822,39 +9681,19 @@ function init() { ,ko: '프로파일 변환 데이터 로딩' ,it: 'Caricamento in corso dati cambio profilo' ,nl: 'Ophalen van data om profiel te wisselen' + ,tr: 'Veri profili değişikliği yükleniyor' ,zh_cn: '载入配置文件交换数据' } - ,'Profile is going to be saved in newer format used in Nightscout 0.9.0 and above and will not be usable in older versions anymore.\nAre you sure?' : { - cs: 'Profil bude uložen v novějším formátu používaném v Nightscoutu 0.9.0 a novějších. Již nebude použitelný se starší verzí.\nJste si jistý?' - ,he: 'הפרופיל עומד להישמר בתבנית חדשה יותר בשימוש ב- Nightscout 0.9.0 ומעלה ולא יהיה ניתן להשתמש בו בגרסאות ישנות יותר. \ האם אתה בטוח? ' - ,el: 'Το προφίλ πρόκειται να αποθηκευτεί με τη νέα του μορφή (έκδοση Nighscout 0.9.0 και πάνω) και δεν πρόκειται να μπορεί να χρησιμοποιηθεί σε παλαιότερες εκδόσεις. \nΕίστε σίγουροι?' - ,fr: 'Le profil va être sauvegardé dans un nouveau format utilisé par Nightscout 0.9.0 et suivants, et il ne pourra plus être utilisé par les versions antérieures. \nÊtes-vous sûr?' - ,ro: 'Profilul va fi salvat într-un format nou, folosit în Nightscout 0.9.0 și superior și nu va mai fi posibilă folosirea pentru versiunile mai vechi.\nSunteți de acord?' - ,de: 'Profil wird in einem neuem Format für Nightscout 0.9.0 und höher gespeichert und ist in älteren Versionen nicht mehr nutzbar. Sind Sie sicher?' - ,dk: 'Profilen gemmes i et nyere format som ikke kommer til at virke med tidligere versioner af Nightscout (<0.9.0). \nEr du sikker?' - ,es: 'El perfil se guarda en un nuevo formato para Nightscout 0.9.0 y superior y ya no se puede usar en versiones anteriores. Estas seguro?' - ,sv: 'Profilen sparas i ett nyare format som ej kommer fungera i tidigare versioner av Nightscout (<0.9.0). \nÄr du säker?' - ,nb: 'Profilen lagres i ett nyere format som ikke kommer til å fungera i tidigere versioner av Nightscout (<0.9.0). \nEr du sikker?' - ,bg: 'Профилът ще бъде запаметен в нов формат, който се ползва от Nightscout 0.9.0 и нагоре и няма да бъде съвместим с по-стари версии. \nСигурен ли си ?' - ,fi: 'Profiili tallennetaan uuteen Nightscout 0.9.0 käyttämään muotoon, eikä sitä voi enää käyttää vanhempien versioiden kanssa.\nOletko varma?' - ,ru: 'Профиль будет сохранен в более позднем формате Nightscout 0.9.0 и выше и не сможет более использоваться в старых версиях. Вы согласны? ' - ,sk: 'Profil bude uložený v novšom formáte používanom od verzie Nightscout 0.9.0 a novších. Nebude už použiteľný v starších verziách.\nSte si istý?' - ,pl: 'Dane profilu będą zapisane w nowym formacie Nighscout 0.9.0 i wyższym. Dane te nie będa mogły być użyte w starszych wersjach.\nJesteś pewien?' - ,pt: 'O perfil será salvo no novo format usado no Nightscout 0.9.0 e acima e não será mais utilizado em versões mais antigas. \nTem certeza de que quer fazer isso?' - ,ko: '프로파일은 Nightscout 0.9.0 에서 새로운 형식으로 저장될 예정입니다. 구버전은 더이상 사용되지 않을 예정입니다. 확인 하셨습니까?' - ,it: 'Profilo sta per essere salvato nel formato più recente utilizzato in Nightscout 0.9.0 e/o superiori e non sarà più possibile utilizzarlo nelle versioni precedenti. \nSei sicuro?' - ,nl: 'Profiel wordt opgeslagen in een nieuw formaat dat gebruikt wordt vanaf Nightscout 0.9.0 hierdoor is deze niet meer bruikbaar voor oudere versies. \nWil je doorgaan?' - ,zh_cn: '配置文件将使用0.9.0版本之后的新格式保存,旧版本程序将无法使用。\n你确定吗?' - } - ,'Wrong profile setting.\nNo profile defined to displayed time.\nRedirecting to profile editor to create new profile.' : { + ,'Redirecting you to the Profile Editor to create a new profile.' : { cs: 'Chybě nastavený profil.\nNení definovaný žádný platný profil k času zobrazení.\nProvádím přesměrování na editor profilu.' - ,he: 'הגדרת פרופיל שגוי. \ N פרופיל מוגדר לזמן המוצג. מפנה מחדש לעורך פרופיל כדי ליצור פרופיל חדש. ' + ,he: 'הגדרת פרופיל שגוי. \n פרופיל מוגדר לזמן המוצג. מפנה מחדש לעורך פרופיל כדי ליצור פרופיל חדש. ' ,el: 'Λάθος προφίλ. Παρακαλώ δημιουργήστε ένα νέο προφίλ' ,fr: 'Erreur de réglage de profil. \nAucun profil défini pour indiquer l\'heure. \nRedirection vers la création d\'un nouveau profil. ' - ,de: 'Falsche Profileinstellung.\nKein Profil festgelegt zur angezeigten Zeit.\n Weiter zum Profileditor, um ein neues Profil zu erstellen.' - ,dk: 'Forkert profilindstilling.\nIngen profil defineret til at vise tid.\nOmdirigerar til profil editoren for at lave en ny profil.' + ,de: 'Sie werden zum Profil-Editor weitergeleitet, um ein neues Profil anzulegen.' + ,dk: 'Forkert profilindstilling.\nIngen profil defineret til at vise tid.\nOmdirigere til profil editoren for at lave en ny profil.' ,es: 'Configuración incorrecta del perfil. \n No establecido ningún perfil en el tiempo mostrado. \n Continuar en editor de perfil para crear perfil nuevo.' ,bg: 'Грешни настройки на профила. \nНяма определен профил към избраното време. \nПрепращане към редактора на профила, за създаване на нов профил.' + ,hr: 'Krive postavke profila.\nNiti jedan profil nije definiran za prikazano vrijeme.\nPreusmjeravanje u editor profila kako biste stvorili novi.' ,ro: 'Setare de profil eronată.\nNu este definit niciun profil pentru perioada afișată.\nMergeți la editorul de profiluri pentru a defini unul nou.' ,sv: 'Fel profilinställning.\nIngen profil vald för vald tid.\nOmdirigerar för att skapa ny profil.' ,nb: 'Feil profilinstilling.\nIngen profil valgt for valgt tid.\nVideresender for å lage ny profil.' @@ -8866,6 +9705,7 @@ function init() { ,ko: '잘못된 프로파일 설정. \n프로파일이 없어서 표시된 시간으로 정의됩니다. 새 프로파일을 생성하기 위해 프로파일 편집기로 리다이렉팅' ,it: 'Impostazione errata del profilo. \nNessun profilo definito per visualizzare l\'ora. \nReindirizzamento al profilo editor per creare un nuovo profilo.' ,nl: 'Verkeerde profiel instellingen.\ngeen profiel beschibaar voor getoonde tijd.\nVerder naar profiel editor om een profiel te maken.' + ,tr: 'Yanlış profil ayarı.\nGörüntülenen zamana göre profil tanımlanmamış.\nYeni profil oluşturmak için profil düzenleyicisine yönlendiriliyor.' ,zh_cn: '配置文件设置错误。\n没有配置文件定义为显示时间。\n返回配置文件编辑器以新建配置文件。' } ,'Pump' : { @@ -8880,6 +9720,7 @@ function init() { ,dk: 'Pumpe' ,es: 'Bomba' ,bg: 'Помпа' + ,hr: 'Pumpa' ,ro: 'Pompă' ,ru: 'Помпа' ,nl: 'Pomp' @@ -8887,6 +9728,7 @@ function init() { ,fi: 'Pumppu' ,pt: 'Bomba' ,it: 'Pompa' + ,tr: 'Pompa' ,zh_cn: '胰岛素泵' ,pl: 'Pompa' } @@ -8899,16 +9741,18 @@ function init() { ,el: 'Ημέρες χρήσης αισθητήρα (SAGE)' ,nb: 'Sensoralder (SAGE)' ,de: 'Sensor-Alter' - ,dk: 'Sensoralder (SAGE)' + ,dk: 'Sensor alder (SAGE)' ,es: 'Días uso del sensor' ,bg: 'Възраст на сензора (ВС)' + ,hr: 'Starost senzora' ,ro: 'Vechimea senzorului' - ,ru: 'Возраст сенсора' + ,ru: 'Сенсор отработал' ,nl: 'Sensor leeftijd' ,ko: '센서 사용 기간' ,fi: 'Sensorin ikä' ,pt: 'Idade do sensor' ,it: 'SAGE - Durata Sensore' + ,tr: '(SAGE) Sensör yaşı ' ,zh_cn: '探头使用时间(SAGE)' ,zh_tw: '探頭使用時間(SAGE)' ,pl: 'Wiek sensora' @@ -8925,13 +9769,15 @@ function init() { ,dk: 'Insulinalder (IAGE)' ,es: 'Días uso cartucho insulina' ,bg: 'Възраст на инсулина (ВИ)' + ,hr: 'Starost inzulina' ,ro: 'Vechimea insulinei' - ,ru: 'инсулин проработал' + ,ru: 'инсулин отработал' ,ko: '인슐린 사용 기간' ,fi: 'Insuliinin ikä' ,pt: 'Idade da insulina' ,it: 'IAGE - Durata Insulina' ,nl: 'Insuline ouderdom (IAGE)' + ,tr: '(IAGE) İnsülin yaşı' ,zh_cn: '胰岛素使用时间(IAGE)' ,zh_tw: '胰島素使用時間(IAGE)' ,pj: 'Wiek insuliny' @@ -8948,6 +9794,7 @@ function init() { ,sv: 'Tillfälligt mål' ,nb: 'Mildertidig mål' ,bg: 'Временна граница' + ,hr: 'Privremeni cilj' ,ro: 'Țintă temporară' ,ru: 'Временная цель' ,nl: 'Tijdelijk doel' @@ -8955,6 +9802,7 @@ function init() { ,fi: 'Tilapäinen tavoite' ,pt: 'Meta temporária' ,it: 'Obiettivo Temporaneo' + ,tr: 'Geçici hedef' ,zh_cn: '临时目标' } ,'Reason' : { @@ -8969,6 +9817,7 @@ function init() { ,dk: 'Årsag' ,es: 'Razonamiento' ,bg: 'Причина' + ,hr: 'Razlog' ,nl: 'Reden' ,ro: 'Motiv' ,ru: 'Причина' @@ -8976,12 +9825,13 @@ function init() { ,fi: 'Syy' ,pt: 'Razão' ,it: 'Ragionare' + ,tr: 'Neden' // Gerekçe ,zh_cn: '原因' ,pl: 'Powód' } ,'Eating soon' : { cs: 'Následuje jídlo' - ,he: 'אוכל בקרוב ' + ,he: 'אוכל בקרוב' ,sk: 'Jesť čoskoro' ,fr: 'Repas sous peu' ,sv: 'Snart matdags' @@ -8990,13 +9840,15 @@ function init() { ,dk: 'Spiser snart' ,es: 'Comer pronto' ,bg: 'Ядене скоро' + ,hr: 'Uskoro jelo' ,ro: 'Mâncare în curând' - ,ru: 'Скоро еда' + ,ru: 'Приближается прием пищи' ,nl: 'Binnenkort eten' ,ko: '편집 중' ,fi: 'Syödään pian' ,pt: 'Refeição em breve' ,it: 'Mangiare prossimamente' + ,tr: 'Yakında yemek' // Kısa zamanda yemek yenecek ,zh_cn: '接近用餐时间' ,pl: 'Zjedz wkrótce' } @@ -9012,6 +9864,7 @@ function init() { ,dk: 'Toppen' ,es: 'Superior' ,bg: 'Горе' + ,hr: 'Vrh' ,ro: 'Deasupra' ,ru: 'Верх' ,nl: 'Boven' @@ -9019,6 +9872,7 @@ function init() { ,fi: 'Ylä' ,pt: 'Superior' ,it: 'Superiore' + ,tr: 'Üst' ,zh_cn: '顶部' ,pl: 'Góra' } @@ -9034,6 +9888,7 @@ function init() { ,dk: 'Bunden' ,es: 'Inferior' ,bg: 'Долу' + ,hr: 'Dno' ,ro: 'Sub' ,ru: 'Низ' ,nl: 'Beneden' @@ -9041,6 +9896,7 @@ function init() { ,fi: 'Ala' ,pt: 'Inferior' ,it: 'Inferiore' + ,tr: 'Alt' //aşağı, alt, düşük ,zh_cn: '底部' ,pl: "Dół" } @@ -9056,6 +9912,7 @@ function init() { ,dk: 'Aktivitet' ,es: 'Actividad' ,bg: 'Активност' + ,hr: 'Aktivnost' ,ro: 'Activitate' ,ru: 'Активность' ,nl: 'Activiteit' @@ -9063,6 +9920,7 @@ function init() { ,fi: 'Aktiviteetti' ,pt: 'Atividade' ,it: 'Attività' + ,tr: 'Aktivite' ,zh_cn: '有效的' ,pl: 'Aktywność' } @@ -9078,6 +9936,7 @@ function init() { ,sv: 'Mål' ,nb: 'Mål' ,bg: 'Граници' + ,hr: 'Ciljevi' ,ro: 'Ținte' ,ru: 'Цели' ,nl: 'Doelen' @@ -9085,6 +9944,7 @@ function init() { ,fi: 'Tavoitteet' ,pt: 'Metas' ,it: 'Obiettivi' + ,tr: 'Hedefler' ,zh_cn: '目标' ,pl: 'Cele' } @@ -9096,9 +9956,10 @@ function init() { ,ro: 'Insulină bolusată:' ,el: 'Ινσουλίνη' ,es: 'Bolo de Insulina' - ,ru: 'Болюс инсулин' + ,ru: 'Болюсный инсулин' ,sv: 'Bolusinsulin:' ,nb: 'Bolusinsulin:' + ,hr: 'Bolus:' ,ko: 'Bolus 인슐린' ,fi: 'Bolusinsuliini:' ,de: 'Bolus Insulin:' @@ -9107,6 +9968,7 @@ function init() { ,sk: 'Bolusový inzulín:' ,it: 'Insulina Bolo' ,nl: 'Bolus insuline' + ,tr: 'Bolus insülin:' ,zh_cn: '大剂量胰岛素' ,pl: 'Bolus insuliny' } @@ -9118,9 +9980,10 @@ function init() { ,ro: 'Bazala obișnuită:' ,el: 'Βασική Ινσουλίνη' ,es: 'Insulina basal básica' - ,ru: 'Основной базал инсулин' + ,ru: 'Основной базальный инсулин' ,sv: 'Basalinsulin:' ,nb: 'Basalinsulin:' + ,hr: 'Osnovni bazal:' ,ko: '기본 basal 인슐린' ,fi: 'Basaalin perusannos:' ,de: 'Basis Basal Insulin:' @@ -9129,6 +9992,7 @@ function init() { ,sk: 'Základný bazálny inzulín:' ,it: 'Insulina Basale:' ,nl: 'Basis basaal insuline' + ,tr: 'Temel bazal insülin' ,zh_cn: '基础率胰岛素' ,pl: 'Bazowa dawka insuliny' } @@ -9143,14 +10007,16 @@ function init() { ,ru: 'Положит знач временн базал инс ' ,sv: 'Positiv tempbasal insulin:' ,nb: 'Positiv midlertidig basalinsulin:' + ,hr: 'Pozitivni temp bazal:' ,ko: '초과된 임시 basal 인슐린' ,fi: 'Positiivinen tilapäisbasaali:' ,de: 'Positives temporäres Basal Insulin:' - ,dk: 'Positiv tempbasal insulin:' + ,dk: 'Positiv midlertidig basalinsulin:' ,pt: 'Insulina basal temporária positiva:' ,sk: 'Pozitívny dočasný bazálny inzulín:' ,it: 'Insulina basale temp positiva:' ,nl: 'Extra tijdelijke basaal insuline' + ,tr: 'Pozitif geçici bazal insülin:' ,zh_cn: '实际临时基础率胰岛素' , pl: 'Zwiększona bazowa dawka insuliny' } @@ -9165,14 +10031,16 @@ function init() { ,sv: 'Negativ tempbasal för insulin:' ,es: 'Insulina basal temporal negativa:' ,nb: 'Negativ midlertidig basalinsulin:' + ,hr: 'Negativni temp bazal:' ,ko: '남은 임시 basal 인슐린' ,fi: 'Negatiivinen tilapäisbasaali:' ,de: 'Negatives temporäres Basal Insulin:' - ,dk: 'Negativ tempbasal insulin:' + ,dk: 'Negativ midlertidig basalinsulin:' ,pt: 'Insulina basal temporária negativa:' ,sk: 'Negatívny dočasný bazálny inzulín:' ,it: 'Insulina basale Temp negativa:' ,nl: 'Negatieve tijdelijke basaal insuline' + ,tr: 'Negatif geçici bazal insülin:' ,zh_cn: '其余临时基础率胰岛素' , pl: 'Zmniejszona bazowa dawka insuliny' } @@ -9187,14 +10055,16 @@ function init() { ,ru: 'Всего базал инсулина' ,sv: 'Total dagsdos basalinsulin:' ,nb: 'Total daglig basalinsulin:' + ,hr: 'Ukupno bazali:' ,ko: '전체 basal 인슐린' ,fi: 'Basaali yhteensä:' ,de: 'Gesamt Basal Insulin:' - ,dk: 'Total basalinsulin:' + ,dk: 'Total daglig basalinsulin:' ,pt: 'Insulina basal total:' ,sk: 'Celkový bazálny inzulín:' ,it: 'Insulina Basale Totale:' ,nl: 'Totaal basaal insuline' + ,tr: 'Toplam bazal insülin:' ,zh_cn: '基础率胰岛素合计' , pl: 'Całkowita ilość bazowej dawki insuliny' } @@ -9205,9 +10075,10 @@ function init() { ,fr: 'Insuline totale journalière' ,ro: 'Insulina totală zilnică:' ,el: 'Συνολική Ημερήσια Ινσουλίνη' - ,ru: 'Всего ежедн базал инсулина' + ,ru: 'Всего суточн базал инсулина' ,sv: 'Total dagsdos insulin' ,nb: 'Total daglig insulin' + ,hr: 'Ukupno dnevni inzulin:' ,ko: '하루 인슐린 총량' ,fi: 'Päivän kokonaisinsuliiniannos' ,de: 'Gesamtes tägliches Insulin:' @@ -9217,6 +10088,7 @@ function init() { ,sk: 'Celkový denný inzulín:' ,it: 'Totale giornaliero d\'insulina:' ,nl: 'Totaal dagelijkse insuline' + ,tr: 'Günlük toplam insülin:' ,zh_cn: '每日胰岛素合计' ,pl: 'Całkowita dzienna ilość insuliny' } @@ -9224,20 +10096,22 @@ function init() { cs: 'Chyba volání %1 Role:' ,he: 'לא יכול תפקיד %1' ,bg: 'Невъзможно да %1 Роля' + ,hr: 'Unable to %1 Role' ,fr: 'Incapable de %1 rôle' ,ro: 'Imposibil de %1 Rolul' ,es: 'Incapaz de %1 Rol' - ,ru: 'Невозможно %1 Роль' + ,ru: 'Невозможно назначить %1 Роль' ,sv: 'Kan inte ta bort roll %1' ,nb: 'Kan ikke %1 rolle' ,fi: '%1 operaatio roolille opäonnistui' ,de: 'Unpassend zu %1 Rolle' - ,dk: 'Kan ikke fjerne %1 rolle' + ,dk: 'Kan ikke slette %1 rolle' ,pt: 'Função %1 não foi possível' ,sk: 'Chyba volania %1 Role' ,ko: '%1로 비활성' ,it: 'Incapace di %1 Ruolo' ,nl: 'Kan %1 rol niet verwijderen' + ,tr: '%1 Rolü yapılandırılamadı' ,zh_cn: '%1角色不可用' ,pl: 'Nie można %1 roli' } @@ -9245,6 +10119,7 @@ function init() { cs: 'Nelze odstranit Roli:' ,he: 'לא יכול לבטל תפקיד ' ,bg: 'Невъзможно изтриването на Роля' + ,hr: 'Ne mogu obrisati ulogu' ,ro: 'Imposibil de șters Rolul' ,fr: 'Effacement de rôle impossible' ,ru: 'Невозможно удалить Роль' @@ -9253,12 +10128,13 @@ function init() { ,nb: 'Kan ikke ta bort rolle' ,fi: 'Roolin poistaminen epäonnistui' ,de: 'Rolle nicht löschbar' - ,dk: 'Kan ikke fjerne rolle' + ,dk: 'Kan ikke slette rolle' ,pt: 'Não foi possível apagar a Função' ,sk: 'Rola sa nedá zmazať' ,ko: '삭제로 비활성' ,it: 'Incapace di eliminare Ruolo' ,nl: 'Niet mogelijk rol te verwijderen' + ,tr: 'Rol silinemedi' ,zh_cn: '无法删除角色' ,pl: 'Nie można usunąć roli' } @@ -9266,6 +10142,7 @@ function init() { cs: 'Databáze obsahuje %1 rolí' ,he: 'בסיס הנתונים מכיל %1 תפקידים ' ,bg: 'Базата данни съдържа %1 роли' + ,hr: 'baza sadrži %1 uloga' ,ro: 'Baza de date conține %1 rol(uri)' ,fr: 'La base de données contient %1 rôles' ,ru: 'База данных содержит %1 Ролей' @@ -9280,6 +10157,7 @@ function init() { ,ko: '데이터베이스가 %1 포함' ,it: 'Database contiene %1 ruolo' ,nl: 'Database bevat %1 rollen' + ,tr: 'Veritabanı %1 rol içerir' ,zh_cn: '数据库包含%1个角色' , pl: 'Baza danych zawiera %1 ról' } @@ -9287,6 +10165,7 @@ function init() { cs:'Editovat roli' ,he: 'ערוך תפקיד ' ,bg: 'Промени Роля' + ,hr: 'Uredi ulogu' ,ro: 'Editează Rolul' ,fr: 'Éditer le rôle' ,ru: 'Редактировать Роль' @@ -9301,6 +10180,7 @@ function init() { ,ko: '편집 모드' ,it: 'Modifica ruolo' ,nl: 'Pas rol aan' + ,tr: 'Rolü düzenle' ,zh_cn: '编辑角色' ,pl: 'Edycja roli' } @@ -9308,6 +10188,7 @@ function init() { cs: 'administrátor, škola, rodina atd...' ,he: 'מנהל, בית ספר, משפחה, וכו ' ,bg: 'администратор,училище,семейство и т.н.' + ,hr: 'admin, škola, obitelj, itd.' ,fr: 'Administrateur, école, famille, etc' ,ro: 'administrator, școală, familie, etc' ,ru: 'админ, школа, семья и т д' @@ -9322,6 +10203,7 @@ function init() { ,ko: '관리자, 학교, 가족 등' ,it: 'amministrazione, scuola, famiglia, etc' ,nl: 'admin, school, familie, etc' + ,tr: 'yönetici, okul, aile, vb' ,zh_cn: '政府、学校、家庭等' ,pl: 'administrator, szkoła, rodzina, itp' } @@ -9329,6 +10211,7 @@ function init() { cs: 'Oprávnění' ,he: 'הרשאות ' ,bg: 'Права' + ,hr: 'Prava' ,ro: 'Permisiuni' ,fr: 'Permissions' ,ru: 'Разрешения' @@ -9343,6 +10226,7 @@ function init() { ,ko: '허가' ,it: 'Permessi' ,nl: 'Rechten' + ,tr: 'İzinler' ,zh_cn: '权限' ,pl: 'Uprawnienia' } @@ -9350,9 +10234,10 @@ function init() { cs: 'Opravdu vymazat: ' ,he: 'אתה בטוח שאתה רוצה למחוק' ,bg: 'Потвърдете изтриването' + ,hr: 'Sigurno želite obrisati?' ,ro: 'Confirmați ștergerea: ' ,fr: 'Êtes-vous sûr de vouloir effacer:' - ,ru: 'Вы уверены, что хотите удалить' + ,ru: 'Подтвердите удаление' ,sv: 'Är du säker att du vill ta bort:' ,nb: 'Er du sikker på at du vil slette:' ,fi: 'Oletko varmat että haluat tuhota: ' @@ -9364,26 +10249,29 @@ function init() { ,ko: '정말로 삭제하시겠습니까: ' ,it: 'Sei sicuro di voler eliminare:' ,nl: 'Weet u het zeker dat u wilt verwijderen?' + ,tr: 'Silmek istediğinizden emin misiniz:' ,zh_cn: '你确定要删除:' ,pl: 'Jesteś pewien, że chcesz usunąć:' } ,'Each role will have a 1 or more permissions. The * permission is a wildcard, permissions are a hierarchy using : as a separator.' : { cs: 'Každá role má 1 nebo více oprávnění. Oprávnění * je zástupný znak, oprávnění jsou hiearchie používající : jako oddělovač.' ,bg: 'Всяка роля ще има 1 или повече права. В * правото е маска, правата са йерархия използвайки : като разделител' + ,hr: 'Svaka uloga ima 1 ili više prava. Pravo * je univerzalno, a prava su hijerarhija koja koristi znak : kao graničnik.' ,fr: 'Chaque rôle aura une ou plusieurs permissions. La permission * est un joker (permission universelle), les permissions sont une hierarchie utilisant : comme séparatuer' ,ro: 'Fiecare rol va avea cel puțin o permisiune. Permisiunea * este totală, permisiunile sunt ierarhice utilizând : pe post de separator.' ,ru: 'Каждая роль имеет 1 или более разрешений. Разрешение * это подстановочный символ, разрешения это иерархия, использующая : как разделитель.' ,sv: 'Varje roll kommer få en eller flera rättigheter. * är en wildcard, rättigheter sätts hirarkiskt med : som avgränsare.' + ,dk: 'Hver rolle vil have en eller flere rettigheder. Rollen * fungere som wildcard / joker, rettigheder sættes hierakisk med : som skilletegn.' ,nb: 'Hver enkelt rolle vil ha en eller flere rettigheter. *-rettigheten er wildcard. Rettigheter settes hierarkisk med : som separator.' ,fi: 'Jokaisella roolilla on yksi tai useampia oikeuksia. * on jokeri (tunnistuu kaikkina oikeuksina), oikeudet ovat hierarkia joka käyttää : merkkiä erottimena.' ,de: 'Jede Rolle hat eine oder mehrere Berechtigungen. Die * Berechtigung ist ein Platzhalter, Berechtigungen sind hierachrchisch mit : als Separator.' - ,sv: 'Hver rolle vil have en eller flere rettigheder. * er en joker, rettigheder sættes hierakisk med : som skilletegn.' ,es: 'Cada Rol tiene uno o más permisos. El permiso * es un marcador de posición y los permisos son jerárquicos con : como separador.' ,pt: 'Cada função terá uma ou mais permissões. A permissão * é um wildcard, permissões são uma hierarquia utilizando * como um separador.' ,sk: 'Každá rola má 1 alebo viac oprávnení. Oprávnenie * je zástupný znak, oprávnenia sú hierarchie používajúce : ako oddelovač.' ,ko: '각각은 1 또는 그 이상의 허가를 가지고 있습니다. 허가는 예측이 안되고 구분자로 : 사용한 계층이 있습니다' ,it: 'Ogni ruolo avrà un 1 o più autorizzazioni. Il * il permesso è un jolly, i permessi sono una gerarchia utilizzando : come separatore.' ,nl: 'Elke rol heeft mintens 1 machtiging. De * machtiging is een wildcard, machtigingen hebben een hyrarchie door gebruik te maken van : als scheidingsteken.' + ,tr: 'Her rolün bir veya daha fazla izni vardır.*izni bir yer tutucudur ve izinler ayırıcı olarak : ile hiyerarşiktir.' ,zh_cn: '每个角色都具有一个或多个权限。权限设置时使用*作为通配符,层次结构使用:作为分隔符。' , pl: 'Każda rola będzie mieć 1 lub więcej uprawnień. Symbol * uprawnia do wszystkiego. Uprawnienia są hierarchiczne, używając : jako separatora.' } @@ -9391,6 +10279,7 @@ function init() { cs: 'Přidat novou roli' ,he: 'הוסף תפקיד חדש ' ,bg: 'Добавете нова роля' + ,hr: 'Dodaj novu ulogu' ,fr: 'Ajouter un nouveau rôle' ,ro: 'Adaugă un Rol nou' ,ru: 'Добавить новую Роль' @@ -9399,12 +10288,13 @@ function init() { ,nb: 'Legg til ny rolle' ,fi: 'Lisää uusi rooli' ,de: 'Eine neue Rolle hinzufügen' - ,dk: 'Tilføj en ny rolle' + ,dk: 'Tilføj ny rolle' ,pt: 'Adicionar novo Papel' ,sk: 'Pridať novú rolu' ,ko: '새 역할 추가' ,it: 'Aggiungere un nuovo ruolo' ,nl: 'Voeg rol toe' + ,tr: 'Yeni Rol ekle' ,zh_cn: '添加新角色' ,pl: 'Dodaj nową rolę' } @@ -9412,6 +10302,7 @@ function init() { cs: 'Role - Skupiny lidí, zařízení atd.' ,he: 'תפקידים - קבוצות של אנשים, התקנים, וכו ' ,bg: 'Роли - Група хора,устройства,т.н.' + ,hr: 'Uloge - Grupe ljudi, uređaja, itd.' ,fr: 'Rôles - Groupe de Personnes ou d\'appareils' ,ro: 'Roluri - Grupuri de persoane, dispozitive, etc' ,es: 'Roles - Grupos de Gente, Dispositivos, etc.' @@ -9426,6 +10317,7 @@ function init() { ,ko: '역할 - 그룹, 기기, 등' ,it: 'Ruoli - gruppi di persone, dispositivi, etc' ,nl: 'Rollen - Groepen mensen apparaten etc' + ,tr: 'Roller - İnsan grupları, Cihazlar vb.' ,zh_cn: '角色 - 一组人或设备等' ,pl: 'Role - Grupy ludzi, urządzeń, itp' } @@ -9433,6 +10325,7 @@ function init() { cs: 'Editovat tuto roli' ,he: 'ערוך תפקיד זה ' ,bg: 'Промени тази роля' + ,hr: 'Uredi ovu ulogu' ,fr: 'Editer ce rôle' ,ro: 'Editează acest rol' ,ru: 'Редактировать эту роль' @@ -9447,6 +10340,7 @@ function init() { ,ko: '이 역할 편집' ,it: 'Modifica questo ruolo' ,nl: 'Pas deze rol aan' + ,tr: 'Bu rolü düzenle' ,zh_cn: '编辑角色' ,pl: 'Edytuj rolę' } @@ -9454,6 +10348,7 @@ function init() { cs: 'Admin autorizován' ,he: 'מנהל אושר ' ,bg: 'Оторизиран като администратор' + ,hr: 'Administrator ovlašten' ,fr: 'Administrateur autorisé' ,ro: 'Autorizat de admin' ,es: 'Administrador autorizado' @@ -9461,13 +10356,14 @@ function init() { ,sv: 'Administratorgodkänt' ,nb: 'Administratorgodkjent' ,fi: 'Ylläpitäjä valtuutettu' - ,de: 'Admin Authorisierung' - ,dk: 'Administratorgodkendt' + ,de: 'als Administrator autorisiert' + ,dk: 'Administrator godkendt' ,pt: 'Administrador autorizado' ,sk: 'Admin autorizovaný' ,ko: '인증된 관리자' ,it: 'Amministrativo autorizzato' ,nl: 'Admin geauthoriseerd' + ,tr: 'Yönetici yetkilendirildi' ,zh_cn: '已授权' ,zh_tw: '已授權' ,pl: 'Administrator autoryzowany' @@ -9476,6 +10372,7 @@ function init() { cs: 'Subjekty - Lidé, zařízení atd.' ,he: 'נושאים - אנשים, התקנים וכו ' ,bg: 'Субекти - Хора,Устройства,т.н.' + ,hr: 'Subjekti - Ljudi, uređaji, itd.' ,fr: 'Utilisateurs - Personnes, Appareils, etc' ,ro: 'Subiecte - Persoane, dispozitive, etc' ,es: 'Sujetos - Personas, Dispositivos, etc' @@ -9490,6 +10387,7 @@ function init() { ,ko: '주제 - 사람, 기기 등' ,it: 'Soggetti - persone, dispositivi, etc' ,nl: 'Onderwerpen - Mensen, apparaten etc' + ,tr: 'Konular - İnsanlar, Cihazlar, vb.' ,zh_cn: '用户 - 人、设备等' ,pl: 'Obiekty - ludzie, urządzenia itp' } @@ -9497,6 +10395,7 @@ function init() { cs: 'Každý subjekt má svůj unikátní token a 1 nebo více rolí. Klikem na přístupový token se otevře nové okno pro tento subjekt. Tento link je možné sdílet.' ,he: 'לכל נושא תהיה אסימון גישה ייחודי ותפקיד אחד או יותר. לחץ על אסימון הגישה כדי לפתוח תצוגה חדשה עם הנושא הנבחר, קישור סודי זה יכול להיות משותף ' ,bg: 'Всеки обект ще има уникален ключ за достъп и 1 или повече роли. Кликнете върху ключа за достъп, за да отворите нов изглед с избрания обект, тази секретна връзка може след това да се споделя' + ,hr: 'Svaki subjekt će dobiti jedinstveni pristupni token i jednu ili više uloga. Kliknite na pristupni token kako bi se otvorio novi pogled sa odabranim subjektom, a tada se tajni link može podijeliti.' ,fr: 'Chaque utilisateur aura un identificateur unique et un ou plusieurs rôles. Cliquez sur l\'identificateur pour révéler l\'utilisateur, ce lien secret peut être partagé.' ,ro: 'Fiecare subiect va avea o cheie unică de acces și unul sau mai multe roluri. Apăsați pe cheia de acces pentru a vizualiza subiectul selectat, acest link poate fi distribuit.' ,ru: 'Каждый субъект получает уникальный код доступа и 1 или более ролей. Нажмите на иконку кода доступа чтобы открыть новое окно с выбранным субъектом, эту секретную ссылку можно переслать.' @@ -9505,12 +10404,13 @@ function init() { ,nb: 'Hver enkelt ressurs får en unik sikkerhetsnøkkel og en eller flere roller. Klikk på sikkerhetsnøkkelen for å åpne valgte ressurs, den hemmelige lenken kan så deles.' ,fi: 'Jokaisella käyttäjällä on uniikki pääsytunniste ja yksi tai useampi rooli. Klikkaa pääsytunnistetta nähdäksesi käyttäjän, jolloin saat jaettavan osoitteen tämän käyttäjän oikeuksilla.' ,de: 'Jedes Subjekt erhält einen einzigartigen Zugriffsschlüssel und eine oder mehrere Rollen. Klicke auf den Zugriffsschlüssel, um eine neue Ansicht mit dem ausgewählten Subjekt zu erhalten. Dieser geheime Link kann geteilt werden.' - ,dk: 'Hvert emne vil have en unik sikkerhedsnøgle og en eller flere roller. Klik på sikkerhedsnøglen for at åbne et nyt view med det valgte emne, dette hemmelige link kan derefter blive delt.' + ,dk: 'Hvert emne vil have en unik sikkerhedsnøgle samt en eller flere roller. Klik på sikkerhedsnøglen for at åbne et nyt view med det valgte emne, dette hemmelige link kan derefter blive delt.' ,pt: 'Cada assunto terá um único token de acesso e uma ou mais Funções. Clique no token de acesso para abrir uma nova visualização com o assunto selecionado. Este link secreto poderá então ser compartilhado' ,sk: 'Každý objekt má svoj unikátny prístupový token a 1 alebo viac rolí. Klikni na prístupový token pre otvorenie nového okna pre tento subjekt. Tento link je možné zdielať.' ,ko: '각 주제는 유일한 access token을 가지고 1 또는 그 이상의 역할을 가질 것이다. 선택된 주제와 새로운 뷰를 열기 위해 access token을 클릭하세요. 이 비밀 링크는 공유되어질 수 있다.' ,it: 'Ogni soggetto avrà un gettone d\'accesso unico e 1 o più ruoli. Fare clic sul gettone d\'accesso per aprire una nuova vista con il soggetto selezionato, questo legame segreto può quindi essere condiviso.' ,nl: 'Elk onderwerp heeft een unieke toegangscode en 1 of meer rollen. Klik op de toegangscode om een nieu venster te openen om het onderwerp te bekijken, deze verborgen link kan gedeeld worden.' + ,tr: 'Her konu benzersiz bir erişim anahtarı ve bir veya daha fazla rol alır. Seçilen konuyla ilgili yeni bir görünüm elde etmek için erişim tuşuna tıklayın. Bu gizli bağlantı paylaşılabilinir.' ,zh_cn: '每个用户具有唯一的访问令牌和一个或多个角色。在访问令牌上单击打开新窗口查看已选择用户,此时该链接可分享。' ,pl: 'Każdy obiekt będzie miał unikalny token dostępu i jedną lub więcej ról. Kliknij token dostępu, aby otworzyć nowy widok z wybranym obiektem, ten tajny link może być następnie udostępniony' } @@ -9518,6 +10418,7 @@ function init() { cs: 'Přidat nový subjekt' ,he: 'הוסף נושא חדש ' ,bg: 'Добави нов субект' + ,hr: 'Dodaj novi subjekt' ,fr: 'Ajouter un nouvel Utilisateur' ,ro: 'Adaugă un Subiect nou' ,ru: 'Добавить нового субъекта' @@ -9532,6 +10433,7 @@ function init() { ,ko: '새 주제 추가' ,it: 'Aggiungere un nuovo Soggetto' ,nl: 'Voeg onderwerp toe' + ,tr: 'Yeni konu ekle' ,zh_cn: '添加新用户' ,pl: 'Dodaj obiekt' } @@ -9539,9 +10441,10 @@ function init() { cs: 'Chyba volání %1 Subjektu:' ,en: 'לא יכול ל %1 נושאי' ,bg: 'Невъзможно %1 субект' + ,hr: 'Ne mogu %1 subjekt' ,ro: 'Imposibil de %1 Subiectul' ,fr: 'Impossible de créer l\'Utilisateur %1' - ,ru: 'Невозможно %1 субъекта' + ,ru: 'Невозможно создать %1 субъект' ,sv: 'Kan ej %1 ämne' ,nb: 'Kan ikke %1 ressurs' ,fi: '%1 operaatio käyttäjälle epäonnistui' @@ -9553,6 +10456,7 @@ function init() { ,ko: '%1 주제 비활성화' ,it: 'Incapace di %1 sottoporre' ,nl: 'Niet in staat %1 Onderwerp' + ,tr: '%1 konu yapılamıyor' ,zh_cn: '%1用户不可用' ,pl: 'Nie można %1 obiektu' } @@ -9560,20 +10464,22 @@ function init() { cs: 'Nelze odstranit Subjekt:' ,he: 'לא יכול לבטל נושא ' ,bg: 'Невъзможно изтриването на субекта' + ,hr: 'Ne mogu obrisati subjekt' ,ro: 'Imposibil de șters Subiectul' ,fr: 'Impossible d\'effacer l\'Utilisateur' - ,ru: 'Невозможно удалить ' + ,ru: 'Невозможно удалить Субъект ' ,sv: 'Kan ej ta bort ämne' ,nb: 'Kan ikke slette ressurs' ,fi: 'Käyttäjän poistaminen epäonnistui' ,de: 'Kann Subjekt nicht löschen' - ,dk: 'Kan ikke fjerne emne' + ,dk: 'Kan ikke slette emne' ,es: 'Imposible eliminar sujeto' ,pt: 'Impossível apagar assunto' ,sk: 'Subjekt sa nedá odstrániť' ,ko: '주제를 지우기 위해 비활성화' ,it: 'Impossibile eliminare Soggetto' ,nl: 'Kan onderwerp niet verwijderen' + ,tr: 'Konu silinemedi' ,zh_cn: '无法删除用户' ,pl: 'Nie można usunąć obiektu' } @@ -9581,6 +10487,7 @@ function init() { cs: 'Databáze obsahuje %1 subjektů' ,he: 'מסד נתונים מכיל %1 נושאים ' ,bg: 'Базата данни съдържа %1 субекти' + ,hr: 'Baza sadrži %1 subjekata' ,fr: 'La base de données contient %1 utilisateurs' ,fi: 'Tietokanta sisältää %1 käyttäjää' ,ru: 'База данных содержит %1 субъект(а/ов)' @@ -9595,6 +10502,7 @@ function init() { ,ko: '데이터베이스는 %1 주제를 포함' ,it: 'Database contiene %1 soggetti' ,nl: 'Database bevat %1 onderwerpen' + ,tr: 'Veritabanı %1 konu içeriyor' ,zh_cn: '数据库包含%1个用户' ,pl: 'Baza danych zawiera %1 obiektów' } @@ -9602,10 +10510,11 @@ function init() { cs:'Editovat subjekt' ,he: 'ערוך נושא ' ,bg: 'Промени субект' + ,hr: 'Uredi subjekt' ,ro: 'Editează Subiectul' ,es: 'Editar sujeto' ,fr: 'Éditer l\'Utilisateur' - ,ru: 'Редактировать субъекта' + ,ru: 'Редактировать Субъект' ,sv: 'Editera ämne' ,nb: 'Editer ressurs' ,fi: 'Muokkaa käyttäjää' @@ -9616,6 +10525,7 @@ function init() { ,ko: '주제 편집' ,it: 'Modifica Oggetto' ,nl: 'Pas onderwerp aan' + ,tr: 'Konuyu düzenle' ,zh_cn: '编辑用户' ,pl: 'Edytuj obiekt' } @@ -9623,6 +10533,7 @@ function init() { cs:'osoba, zařízeni atd.' ,he: 'אדם, מכשיר, וכו ' ,bg: 'човек,устройство,т.н.' + ,hr: 'osoba, uređaj, itd.' ,ro: 'persoană, dispozitiv, etc' ,fr: 'personne, appareil, etc' ,ru: 'лицо, устройство и т п' @@ -9637,6 +10548,7 @@ function init() { ,ko: '사람, 기기 등' ,it: 'persona, dispositivo, ecc' ,nl: 'persoon, apparaat etc' + ,tr: 'kişi, cihaz, vb' ,zh_cn: '人、设备等' ,pl: 'osoba, urządzenie, itp' } @@ -9644,6 +10556,7 @@ function init() { cs:'role1, role2' ,he: 'תפקיד1, תפקיד2 ' ,bg: 'Роля1, Роля2' + ,hr: 'uloga1, uloga2' ,ro: 'Rol1, Rol2' ,fr: 'rôle1, rôle2' ,ru: 'Роль1, Роль2' @@ -9658,6 +10571,7 @@ function init() { ,ko: '역할1, 역할2' ,it: 'ruolo1, ruolo2' ,nl: 'rol1, rol2' + ,tr: 'rol1, rol2' ,zh_cn: '角色1、角色2' ,pl: 'rola1, rola2' } @@ -9665,9 +10579,10 @@ function init() { cs:'Editovat tento subjekt' ,he: 'ערוך נושא זה ' ,bg: 'Промени този субект' + ,hr: 'Uredi ovaj subjekt' ,ro: 'Editează acest subiect' ,fr: 'Éditer cet utilisateur' - ,ru: 'Редактировать этого субъекта' + ,ru: 'Редактировать этот субъект' ,sv: 'Editera ämnet' ,es: 'Editar este sujeto' ,nb: 'Editer ressurs' @@ -9679,6 +10594,7 @@ function init() { ,ko: '이 주제 편집' ,it: 'Modifica questo argomento' ,nl: 'pas dit onderwerp aan' + ,tr: 'Bu konuyu düzenle' ,zh_cn: '编辑此用户' ,pl: 'Edytuj ten obiekt' } @@ -9686,9 +10602,10 @@ function init() { cs:'Smazat tento subjekt' ,he: 'בטל נושא זה ' ,bg: 'Изтрий този субект' + ,hr: 'Obriši ovaj subjekt' ,ro: 'Șterge acest subiect' ,fr: 'Effacer cet utilisateur:' - ,ru: 'Удалить этого субъекта' + ,ru: 'Удалить этот субъект' ,sv: 'Ta bort ämnet' ,nb: 'Slett ressurs' ,fi: 'Poista tämä käyttäjä' @@ -9700,6 +10617,7 @@ function init() { ,ko: '이 주제 삭제' ,it: 'Eliminare questo argomento' ,nl: 'verwijder dit onderwerp' + ,tr: 'Bu konuyu sil' ,zh_cn: '删除此用户' ,pl: 'Usuń ten obiekt' } @@ -9707,6 +10625,7 @@ function init() { cs:'Role' ,he: 'תפקידים ' ,bg: 'Роли' + ,hr: 'Uloge' ,ro: 'Roluri' ,fr: 'Rôles' ,ru: 'Роли' @@ -9721,6 +10640,7 @@ function init() { ,ko: '역할' ,it: 'Ruoli' ,nl: 'Rollen' + ,tr: 'Roller' ,zh_cn: '角色' ,pl: 'Role' } @@ -9728,6 +10648,7 @@ function init() { cs:'Přístupový token' ,he: 'אסימון גישה ' ,bg: 'Ключ за достъп' + ,hr: 'Pristupni token' ,ro: 'Cheie de acces' ,fr: 'Identificateur unique' ,ru: 'Код доступа' @@ -9742,6 +10663,7 @@ function init() { ,ko: 'Access Token' ,it: 'Gettone d\'accesso' ,nl: 'toegangscode' + ,tr: 'Erişim Simgesi (Access Token)' ,zh_cn: '访问令牌' ,pl: 'Token dostępu' } @@ -9749,6 +10671,7 @@ function init() { cs:'hodina zpět' ,he: 'לפני שעה ' ,bg: 'Преди час' + ,hr: 'sat ranije' ,fr: 'heure avant' ,ro: 'oră în trecut' ,ru: 'час назад' @@ -9763,6 +10686,7 @@ function init() { ,ko: '시간 후' ,it: 'ora fa' ,nl: 'uur geleden' + ,tr: 'saat önce' ,zh_cn: '小时前' ,pl: 'Godzię temu' } @@ -9770,6 +10694,7 @@ function init() { cs:'hodin zpět' ,he: 'לפני שעות ' ,bg: 'Преди часове' + ,hr: 'sati ranije' ,fr: 'heures avant' ,ro: 'ore în trecut' ,ru: 'часов назад' @@ -9784,6 +10709,7 @@ function init() { ,ko: '시간 후' ,it: 'ore fa' ,nl: 'uren geleden' + ,tr: 'saat önce' ,zh_cn: '小时前' ,pl: 'Godzin temu' } @@ -9791,6 +10717,7 @@ function init() { cs:'Ztlumit na %1 minut' ,he: 'שקט ל %1 דקות ' ,bg: 'Заглушаване за %1 минути' + ,hr: 'Utišaj na %1 minuta' ,ro: 'Liniște pentru %1 minute' ,fr: 'Silence pour %1 minutes' ,ru: 'Молчание %1 минут' @@ -9805,6 +10732,7 @@ function init() { ,ko: '%1 분 동안 무음' ,it: 'Silenzio per %1 minuti' ,nl: 'Sluimer %1 minuten' + ,tr: '%1 dakika sürelik sessizlik' ,zh_cn: '静音%1分钟' ,zh_tw: '靜音%1分鐘' ,pl: 'Wycisz na %1 minut' @@ -9813,7 +10741,7 @@ function init() { cs:'Zkontrolovat glykémii' ,he: 'בדוק סוכר בדם ' ,de: 'BZ kontrollieren' - ,dk: 'Kontroler blodglukos' + ,dk: 'Kontroller blodsukker' ,ro: 'Verificați glicemia' ,es: 'Verificar glucemia' ,fr: 'Vérifier la glycémie' @@ -9824,9 +10752,11 @@ function init() { ,fi: 'Tarkista VS' ,pt: 'Verifique a glicemia' ,bg: 'Проверка КЗ' + ,hr: 'Provjeri GUK' ,ko: '혈당 체크' ,it: 'Controllare BG' ,nl: 'Controleer BG' + ,tr: 'KŞ\'ini kontrol et' ,zh_cn: '测量血糖' ,pl: 'Sprawdź glukozę z krwi' } @@ -9844,10 +10774,12 @@ function init() { ,fi: 'Basaali' ,pt: 'BASAL' ,bg: 'Базал' + ,hr: 'Bazal' ,ko: 'BASAL' ,es: 'BASAL' ,it: 'BASALE' ,nl: 'Basaal' + ,tr: 'Bazal' ,zh_cn: '基础率' ,zh_tw: '基礎率' ,pl: 'BAZA' @@ -9867,9 +10799,11 @@ function init() { ,pt: 'Basal atual' ,es: 'Basal actual' ,bg: 'Актуален базал' + ,hr: 'Trenutni bazal' ,ko: '현재 basal' ,it: 'Basale corrente' ,nl: 'Huidige basaal' + ,tr: 'Geçerli Bazal' ,zh_cn: '当前基础率' ,pl: 'Dawka podstawowa' } @@ -9880,7 +10814,7 @@ function init() { ,dk: 'Insulinfølsomhed (ISF)' ,ro: 'Sensibilitate la insulină (ISF)' ,fr: 'Sensibilité à l\'insuline (ISF)' - ,ru: 'Чуствительность к инсулину' + ,ru: 'Чуствительность к инсулину ISF' ,sk: 'Citlivosť (ISF)' ,sv: 'Insulinkönslighet (ISF)' ,nb: 'Insulinsensitivitet (ISF)' @@ -9888,9 +10822,11 @@ function init() { ,es: 'Sensibilidad' ,pt: 'Fator de sensibilidade' ,bg: 'Инсулинова чувствителност (ISF)' + ,hr: 'Osjetljivost' ,ko: '인슐린 민감도(ISF)' ,it: 'ISF - sensibilità' ,nl: 'Gevoeligheid' + ,tr: 'Duyarlılık Faktörü (ISF)' ,zh_cn: '胰岛素敏感系数' ,pl: 'Wrażliwość' } @@ -9901,7 +10837,7 @@ function init() { ,dk: 'Nuværende kulhydratratio' ,fr: 'Rapport Insuline-glucides actuel (I:C)' ,ro: 'Raport Insulină:Carbohidrați (ICR)' - ,ru: 'Актуальное соотношение инсулин:углеводы' + ,ru: 'Актуальное соотношение инсулин:углеводы I:C' ,sk: 'Aktuálny sacharidový pomer (I"C)' ,sv: 'Gällande kolhydratkvot' ,nb: 'Gjeldende karbohydratforhold' @@ -9909,9 +10845,11 @@ function init() { ,pt: 'Relação insulina:carboidrato atual' ,fi: 'Nykyinen hiilihydraattiherkkyys' ,bg: 'Актуално Въглехидратно Съотношение' + ,hr: 'Trenutni I:C' ,ko: '현재 탄수화물 비율(ICR)' ,it: 'Attuale rapporto I:C' ,nl: 'Huidige KH ratio' + ,tr: 'Geçerli Karbonhidrat oranı I/C (ICR)' ,zh_cn: '当前碳水化合物系数' ,pl: 'Obecny przelicznik węglowodanowy' } @@ -9923,16 +10861,18 @@ function init() { ,ro: 'Fus orar pentru bazală' ,es: 'Zona horaria basal' ,fr: 'Fuseau horaire' - ,ru: 'Временная зона базала' + ,ru: 'Часовой пояс базала' ,sk: 'Časová zóna pre bazál' ,sv: 'Basal tidszon' ,nb: 'Basal tidssone' ,pt: 'Fuso horário da basal' ,fi: 'Aikavyöhyke' ,bg: 'Базална часова зона' + ,hr: 'Vremenska zona bazala' ,ko: 'Basal 타임존' ,it: 'fuso orario basale' ,nl: 'Basaal tijdzone' + ,tr: 'Bazal saat dilimi' ,zh_cn: '基础率时区' ,pl: 'Strefa czasowa dawki podstawowej' } @@ -9951,9 +10891,11 @@ function init() { ,es: 'Perfil activo' ,fi: 'Aktiivinen profiili' ,bg: 'Активен профил' + ,hr: 'Aktivni profil' ,ko: '활성 프로파일' ,it: 'Attiva profilo' ,nl: 'Actief profiel' + ,tr: 'Aktif profil' ,zh_cn: '当前配置文件' ,pl: 'Profil aktywny' } @@ -9961,7 +10903,7 @@ function init() { cs:'Aktivní dočasný bazál' ,he: 'רמה בזלית זמנית פעילה ' ,de: 'Aktive temp. Basalrate' - ,dk: 'Aktiv tempbasal' + ,dk: 'Aktiv midlertidig basal' ,ro: 'Bazală temporară activă' ,fr: 'Débit basal temporaire actif' ,ru: 'Активный временный базал' @@ -9972,9 +10914,11 @@ function init() { ,es: 'Basal temporal activa' ,fi: 'Aktiivinen tilapäisbasaali' ,bg: 'Активен временен базал' + ,hr: 'Aktivni temp bazal' ,ko: '활성 임시 basal' ,it: 'Attiva Basale Temp' ,nl: 'Actieve tijdelijke basaal' + ,tr: 'Aktif geçici bazal oranı' ,zh_cn: '当前临时基础率' ,pl: 'Aktywa tymczasowa dawka podstawowa' } @@ -9982,7 +10926,7 @@ function init() { cs:'Začátek dočasného bazálu' ,he: 'התחלה רמה בזלית זמנית ' ,de: 'Start aktive temp. Basalrate' - ,dk: 'Aktiv tempbasal start' + ,dk: 'Aktiv midlertidig basal start' ,ro: 'Start bazală temporară activă' ,fr: 'Début du débit basal temporaire' ,ru: 'Старт активного временного базала' @@ -9993,9 +10937,11 @@ function init() { ,es: 'Inicio Basal temporal activa' ,fi: 'Aktiivisen tilapäisbasaalin aloitus' ,bg: 'Старт на активен временен базал' + ,hr: 'Početak aktivnog tamp bazala' ,ko: '활성 임시 basal 시작' ,it: 'Attiva Inizio Basale temp' ,nl: 'Actieve tijdelijke basaal start' + ,tr: 'Aktif geçici bazal oranı başlangıcı' ,zh_cn: '当前临时基础率开始' ,pl: 'Start aktywnej tymczasowej dawki podstawowej' } @@ -10003,7 +10949,7 @@ function init() { cs:'Trvání dočasného bazálu' ,he: 'משך רמה בזלית זמנית ' ,de: 'Dauer aktive temp. Basalrate' - ,dk: 'Aktiv tempbasal varighed' + ,dk: 'Aktiv midlertidig basal varighed' ,ro: 'Durata bazalei temporare active' ,fr: 'Durée du débit basal temporaire' ,ru: 'Длительность активного временного базала' @@ -10014,17 +10960,19 @@ function init() { ,es: 'Duración Basal Temporal activa' ,fi: 'Aktiivisen tilapäisbasaalin kesto' ,bg: 'Продължителност на Активен временен базал' + ,hr: 'Trajanje aktivnog temp bazala' ,ko: '활성 임시 basal 시간' ,it: 'Attiva durata basale temp' ,nl: 'Actieve tijdelijk basaal duur' ,zh_cn: '当前临时基础率期间' ,pl: 'Czas trwania aktywnej tymczasowej dawki podstawowej' + ,tr: 'Aktif geçici bazal süresi' } ,'Active temp basal remaining' : { cs:'Zbývající dočasný bazál' ,he: 'זמן שנשאר ברמה בזלית זמנית ' ,de: 'Verbleibene Dauer temp. Basalrate' - ,dk: 'Resterende aktiv tempbasal' + ,dk: 'Resterende tid for aktiv midlertidig basal' ,ro: 'Rest de bazală temporară activă' ,fr: 'Durée restante de débit basal temporaire' ,ru: 'Остаток акивного временного базала' @@ -10035,11 +10983,13 @@ function init() { ,es: 'Basal temporal activa restante' ,fi: 'Aktiivista tilapäisbasaalia jäljellä' ,bg: 'Оставащ Активен временен базал' + ,hr: 'Prestali aktivni temp bazal' ,ko: '남아 있는 활성 임시 basal' ,it: 'Attiva residuo basale temp' ,nl: 'Actieve tijdelijke basaal resteert' ,zh_cn: '当前临时基础率剩余' ,pl: 'Pozostała aktywna tymczasowa dawka podstawowa' + ,tr: 'Aktif geçici bazal kalan' } ,'Basal profile value' : { cs: 'Základní hodnota bazálu' @@ -10047,7 +10997,7 @@ function init() { ,de: 'Basal-Profil Wert' ,dk: 'Basalprofil værdi' ,ro: 'Valoarea profilului bazalei' - ,ru: 'Значение профайла базала' + ,ru: 'Величина профильного базала' ,fr: 'Valeur du débit basal' ,sv: 'Basalprofil värde' ,es: 'Valor perfil Basal' @@ -10056,11 +11006,13 @@ function init() { ,pt: 'Valor do perfil basal' ,sk: 'Základná hodnota bazálu' ,bg: 'Базален профил стойност' + ,hr: 'Profilna vrijednost bazala' ,ko: 'Basal 프로파일 값' ,it: 'Valore profilo basale' ,nl: 'Basaal profiel waarde' ,zh_cn: '基础率配置文件值' ,pl: 'Wartość profilu podstawowego' + ,tr: 'Bazal profil değeri' } ,'Active combo bolus' : { cs:'Aktivní kombinovaný bolus' @@ -10077,11 +11029,13 @@ function init() { ,es: 'Activo combo-bolo' ,sk: 'Aktívny kombinovaný bolus' ,bg: '' + ,hr: 'Aktivni dual bolus' ,ko: '활성 콤보 bolus' ,it: 'Attiva Combo bolo' ,nl: 'Actieve combo bolus' ,zh_cn: '当前双波大剂量' ,pl: 'Aktywny bolus złożony' + ,tr: 'Aktive kombo bolus' } ,'Active combo bolus start' : { cs: 'Začátek kombinovaného bolusu' @@ -10098,11 +11052,13 @@ function init() { ,es: 'Inicio del combo-bolo activo' ,sk: 'Štart kombinovaného bolusu' ,bg: 'Активен комбиниран болус' + ,hr: 'Početak aktivnog dual bolusa' ,ko: '활성 콤보 bolus 시작' ,it: 'Attivo inizio Combo bolo' ,nl: 'Actieve combo bolus start' ,zh_cn: '当前双波大剂量开始' ,pl: 'Start aktywnego bolusa złożonego' + ,tr: 'Aktif gecikmeli bolus başlangıcı' } ,'Active combo bolus duration' : { cs: 'Trvání kombinovaného bolusu' @@ -10119,11 +11075,13 @@ function init() { ,pt: 'Duração de bolus duplo em atividade' ,sk: 'Trvanie kombinovaného bolusu' ,bg: 'Продължителност на активния комбиниран болус' + ,hr: 'Trajanje aktivnog dual bolusa' ,ko: '활성 콤보 bolus 기간' ,it: 'Attivo durata Combo bolo' ,nl: 'Actieve combo bolus duur' ,zh_cn: '当前双波大剂量期间' ,pl: 'Czas trwania aktywnego bolusa złożonego' + ,tr: 'Active combo bolus süresi' } ,'Active combo bolus remaining' : { cs: 'Zbývající kombinovaný bolus' @@ -10140,11 +11098,13 @@ function init() { ,pt: 'Restante de bolus duplo em atividade' ,sk: 'Zostávajúci kombinovaný bolus' ,bg: 'Оставащ активен комбиниран болус' + ,hr: 'Preostali aktivni dual bolus' ,ko: '남아 있는 활성 콤보 bolus' ,it: 'Attivo residuo Combo bolo' ,nl: 'Actieve combo bolus resteert' ,zh_cn: '当前双波大剂量剩余' ,pl: 'Pozostały aktywny bolus złożony' + ,tr: 'Aktif kombo (yayım) bolus kaldı' } ,'BG Delta' : { cs:'Změna glykémie' @@ -10161,12 +11121,14 @@ function init() { ,es: 'Diferencia de glucemia' ,sk: 'Zmena glykémie' ,bg: 'Изменение КЗ' + ,hr: 'GUK razlika' ,ko: '혈당 차이' ,it: 'BG Delta' ,nl: 'BG Delta' ,zh_cn: '血糖增量' ,zh_tw: '血糖增量' ,pl: 'Zmiana glikemii' + ,tr: 'KŞ farkı' } ,'Elapsed Time' : { cs:'Dosažený čas' @@ -10183,12 +11145,14 @@ function init() { ,es: 'Tiempo transcurrido' ,sk: 'Uplynutý čas' ,bg: 'Изминало време' + ,hr: 'Proteklo vrijeme' ,ko: '경과 시간' ,it: 'Tempo Trascorso' ,nl: 'Verstreken tijd' ,zh_cn: '所需时间' ,zh_tw: '所需時間' ,pl: 'Upłynął czas' + ,tr: 'Geçen zaman' } ,'Absolute Delta' : { cs:'Absolutní rozdíl' @@ -10205,12 +11169,14 @@ function init() { ,es: 'Diferencia absoluta' ,sk: 'Absolútny rozdiel' ,bg: 'Абсолютно изменение' + ,hr: 'Apsolutna razlika' ,ko: '절대적인 차이' ,it: 'Delta Assoluto' ,nl: 'Abslute delta' ,zh_cn: '绝对增量' ,zh_tw: '絕對增量' ,pl: 'różnica absolutna' + ,tr: 'Mutlak fark' } ,'Interpolated' : { cs:'Interpolováno' @@ -10227,12 +11193,14 @@ function init() { ,es: 'Interpolado' ,sk: 'Interpolované' ,bg: 'Интерполирано' + ,hr: 'Interpolirano' ,ko: '삽입됨' ,it: 'Interpolata' ,nl: 'Geinterpoleerd' ,zh_cn: '插值' ,zh_tw: '插值' ,pl: 'Interpolowany' + ,tr: 'Aralıklı' } ,'BWP' : { // Bolus Wizard Preview cs: 'KALK' @@ -10240,11 +11208,12 @@ function init() { ,de: 'BWP' ,dk: 'Bolusberegner (BWP)' ,ro: 'Ajutor bolusare (BWP)' - ,ru: 'Предпросмотр калькулятора болюса' + ,ru: 'Калькулятор болюса' ,fr: 'Calculateur de bolus (BWP)' ,sv: 'Boluskalkylator' ,es: 'VistaPreviaCalculoBolo (BWP)' ,nb: 'Boluskalkulator' + ,hr: 'Čarobnjak bolusa' ,fi: 'Annoslaskuri' ,pt: 'Ajuda de bolus' ,sk: 'BK' @@ -10255,6 +11224,7 @@ function init() { ,zh_cn: 'BWP' ,zh_tw: 'BWP' ,pl: 'Kalkulator bolusa' + ,tr: 'BWP' } ,'Urgent' : { cs:'Urgentní' @@ -10271,12 +11241,15 @@ function init() { ,pt: 'Urgente' ,sk: 'Urgentné' ,bg: 'Спешно' + ,hr: 'Hitno' ,ko: '긴급' ,it: 'Urgente' + ,ja: '緊急' ,nl: 'Urgent' ,zh_cn: '紧急' ,zh_tw: '緊急' ,pl:'Pilny' + ,tr: 'Acil' } ,'Warning' : { cs:'Varování' @@ -10293,12 +11266,14 @@ function init() { ,es: 'Aviso' ,sk: 'Varovanie' ,bg: 'Предупреждение' + ,hr: 'Upozorenje' ,ko: '경고' ,it: 'Avviso' ,nl: 'Waarschuwing' ,zh_cn: '警告' ,zh_tw: '警告' ,pl: 'Ostrzeżenie' + ,tr: 'Uyarı' } ,'Info' : { cs: 'Informativní' @@ -10315,12 +11290,14 @@ function init() { ,es: 'Información' ,sk: 'Info' ,bg: 'Информация' + ,hr: 'Info' ,ko: '정보' ,it: 'Info' ,nl: 'Info' ,zh_cn: '信息' ,zh_tw: '資訊' ,pl: 'Informacja' + ,tr: 'Info' } ,'Lowest' : { cs: 'Nejnižší' @@ -10337,9 +11314,11 @@ function init() { ,pt: 'Mais baixo' ,sk: 'Najnižsie' ,bg: 'Най-ниско' + ,hr: 'Najniže' ,ko: '가장 낮은' ,it: 'Minore' ,nl: 'Laagste' + ,tr: 'En düşük değer' ,zh_cn: '血糖极低' ,zh_tw: '血糖極低' ,pl: 'Niski' @@ -10348,10 +11327,11 @@ function init() { cs:'Vypínání alarmu vyskoké glykémie, protože je dostatek IOB' ,he: 'נודניק את ההתראה הגבוהה מפני שיש מספיק אינסולין פעיל בגוף' ,de: 'Ignoriere Alarm hoch, da genügend aktives Bolus-Insulin (IOB) vorhanden' - ,dk: 'Snoozer høj alarm siden der er nok aktiv insulin (IOB)' + ,dk: 'Udsætter høj alarm siden der er nok aktiv insulin (IOB)' ,ro: 'Ignoră alarma de hiper deoarece este suficientă insulină activă IOB' ,fr: 'Alarme haute ignorée car suffisamment d\'insuline à bord (IOB)' ,bg: 'Изключване на аларма за висока КЗ, тъй като има достатъчно IOB' + ,hr: 'Stišan alarm za hiper pošto ima dovoljno aktivnog inzulina' ,es: 'Ignorar alarma de Hiperglucemia al tener suficiente insulina activa' ,ru: 'Отключение предупреждение о высоком СК ввиду достаточности инсулина в организме' ,sv: 'Snoozar höglarm då aktivt insulin är tillräckligt' @@ -10359,8 +11339,10 @@ function init() { ,fi: 'Korkean sokerin varoitus poistettu riittävän insuliinin vuoksi' ,pt: 'Ignorar alarme de hiper em função de IOB suficiente' ,sk: 'Odloženie alarmu vysokej glykémie, pretože je dostatok IOB' + ,ko: '충분한 IOB가 남아 있기 때문에 고혈당 알람을 스누즈' ,it: 'Addormenta allarme alto poiché non vi è sufficiente IOB' ,nl: 'Snooze hoog alarm, er is genoeg IOB' + ,tr: 'Yeterli IOB(Aktif İnsülin) olduğundan KŞ yüksek uyarımını ertele' ,zh_cn: '有足够的IOB(活性胰岛素),暂停高血糖警报' ,pl: 'Wycisz alarm wysokiej glikemi, jest wystarczająco dużo aktywnej insuliny' } @@ -10372,15 +11354,18 @@ function init() { ,ro: 'Verifică glicemia, poate este necesar un bolus?' ,fr: 'Vérifier la glycémie, bolus nécessaire ?' ,bg: 'Провери КЗ, не е ли време за болус?' - ,ru: 'Проверьте СК, не пора ли ввести болюс?' + ,hr: 'Provjeri GUK, vrijeme je za bolus?' + ,ru: 'Проверьте СК, дать болюс?' ,sv: 'Kontrollera BS, dags att ge bolus?' ,nb: 'Sjekk blodsukker, på tide med bolus?' ,fi: 'Tarkista VS, aika bolustaa?' ,pt: 'Meça a glicemia, hora de bolus de correção?' ,es: 'Controle su glucemia, ¿quizás un bolo Insulina?' ,sk: 'Skontrolovať glykémiu, čas na bolus?' + ,ko: 'Bolus를 주입할 시간입니다. 혈당 체크 하시겠습니까?' ,it: 'Controllare BG, il tempo del bolo?' ,nl: 'Controleer BG, tijd om te bolussen?' + ,tr: 'KŞine bakın, gerekirse bolus verin?' ,zh_cn: '测量血糖,该输注大剂量了?' ,pl: 'Sprawdź glikemię, czas na bolusa ?' } @@ -10393,6 +11378,7 @@ function init() { ,ru: 'Заметка' ,fr: 'Notification' ,bg: 'Известие' + ,hr: 'Poruka' ,sv: 'Notering' ,es: 'Nota' ,nb: 'NB' @@ -10400,7 +11386,9 @@ function init() { ,nl: 'Notitie' ,pt: 'Nota' ,sk: 'Poznámka' + ,ko: '알림' ,it: 'Preavviso' + ,tr: 'Not' ,zh_cn: '提示' ,pl: 'Uwaga' } @@ -10413,14 +11401,17 @@ function init() { ,fr: 'Information nécessaire manquante' ,ru: 'Отсутствует необходимая информация' ,bg: 'Липсва необходима информация' + ,hr: 'nedostaju potrebne informacije' ,sv: 'Nödvändig information saknas' ,nb: 'Nødvendig informasjon mangler' ,es: 'Falta información requerida' ,fi: 'tarvittava tieto puuttuu' ,pt: 'Informação essencial faltando' ,sk: 'chýbajúca informácia' + ,ko: '요청한 정보 손실' ,it: 'richiesta informazioni mancanti' ,nl: 'vereiste gegevens ontbreken' + ,tr: 'gerekli bilgi eksik' ,zh_cn: '所需信息不全' ,pl: 'brak wymaganych informacji' } @@ -10433,14 +11424,17 @@ function init() { ,fr: 'Insuline à bord (IOB)' ,ru: 'Активный инсулин (IOB)' ,bg: 'Активен Инсулин (IOB)' + ,hr: 'Aktivni inzulín' ,sv: 'Aktivt insulin (IOB)' ,nb: 'Aktivt insulin (IOB)' ,fi: 'Aktiivinen insuliini' ,pt: 'Insulina ativa' ,es: 'Insulina activa (IOB)' ,sk: 'Aktívny inzulín (IOB)' + ,ko: '활성 인슐린(IOB)' ,it: 'IOB - Insulina Attiva' ,nl: 'IOB - Inuline on board' + ,tr: '(IOB) Aktif İnsülin' ,zh_cn: '活性胰岛素(IOB)' ,pl: 'Aktywna insulina' } @@ -10453,14 +11447,17 @@ function init() { ,fr: 'Cible actuelle' ,ru: 'Актуальное целевое значение' ,bg: 'Настояща целева КЗ' + ,hr: 'Trenutni cilj' ,sv: 'Aktuellt mål' ,nb: 'Gjeldende mål' ,fi: 'Tämänhetkinen tavoite' ,es: 'Objetivo actual' ,pt: 'Meta atual' ,sk: 'Aktuálny cieľ' + ,ko: '현재 목표' ,it: 'Obiettivo attuale' ,nl: 'huidig doel' + ,tr: 'Mevcut hedef' ,zh_cn: '当前目标' ,pl: 'Aktualny cel' } @@ -10473,14 +11470,17 @@ function init() { ,fr: 'Effect escompté' ,ru: 'Ожидаемый эффект' ,bg: 'Очакван ефект' + ,hr: 'Očekivani efekt' ,sv: 'Förväntad effekt' ,nb: 'Forventet effekt' ,fi: 'Oletettu vaikutus' ,pt: 'Efeito esperado' ,es: 'Efecto previsto' ,sk: 'Očakávaný efekt' + ,ko: '예상 효과' ,it: 'Effetto Previsto' ,nl: 'verwacht effect' + ,tr: 'Beklenen etki' ,zh_cn: '预期效果' ,pl: 'Oczekiwany efekt' } @@ -10493,14 +11493,17 @@ function init() { ,fr: 'Résultat escompté' ,ru: 'Ожидаемый результат' ,bg: 'Очакван резултат' + ,hr: 'Očekivani ishod' ,sv: 'Förväntat resultat' ,nb: 'Forventet resultat' ,fi: 'Oletettu lopputulos' ,pt: 'Resultado esperado' ,es: 'Resultado previsto' ,sk: 'Očakávaný výsledok' + ,ko: '예상 결과' ,it: 'Risultato previsto' ,nl: 'Veracht resultaat' + ,tr: 'Beklenen sonuç' ,zh_cn: '预期结果' ,pl: 'Oczekowany resultat' } @@ -10513,6 +11516,7 @@ function init() { ,fr: 'Equivalent glucidique' ,ru: 'Эквивалент в углеводах' ,bg: 'Равностойност във ВХ' + ,hr: 'Ekvivalent u UGH' ,sv: 'Kolhydratsinnehåll' ,nb: 'Karbohydratekvivalent' ,nl: 'Koolhydraat equivalent' @@ -10520,7 +11524,9 @@ function init() { ,es: 'Equivalencia en Carbohidratos' ,pt: 'Equivalente em carboidratos' ,sk: 'Sacharidový ekvivalent' + ,ko: '탄수화물 양' ,it: 'Carb equivalenti' + ,tr: 'Karbonhidrat eşdeğeri' ,zh_cn: '碳水当量' ,pl: 'Odpowiednik w węglowodanach' } @@ -10528,20 +11534,22 @@ function init() { cs:'Nadbytek inzulínu: o %1U více, než na dosažení spodní hranice cíle. Nepočítáno se sacharidy.' ,he: 'עודף אינסולין שווה ערך ליחידה אחת%1 יותר מאשר הצורך להגיע ליעד נמוך, לא לוקח בחשבון פחמימות ' ,de: 'Überschüssiges Insulin: %1U mehr als zum Erreichen der Untergrenze notwendig, Kohlenhydrate sind unberücksichtigt' - ,dk: 'Overskud af insulin modsvarande %1U mere end nødvændigt for at nå lav målværdi, kulhydrater ikke medregnet' + ,dk: 'Overskud af insulin modsvarende %1U mere end nødvendigt for at nå lav mål, kulhydrater ikke medregnet' ,es: 'Exceso de insulina en %1U más de lo necesario para alcanzar un objetivo bajo, sin tener en cuenta los carbohidratos' ,ro: 'Insulină în exces: %1U mai mult decât este necesar pentru a atinge ținta inferioară, fără a ține cont de carbohidrați' ,fr: 'Insuline en excès: %1U de plus que nécessaire pour atteindre la cible inférieure, sans prendre en compte les glucides' ,bg: 'Излишният инсулин %1U е повече от необходимия за достигане до долната граница, ВХ не се вземат под внимание' - ,ru: 'Избыток инсулина равного %1U, необходимого для достижения нижнего целевого значения, углеводы не будут учтены' + ,hr: 'Višak inzulina je %1U više nego li je potrebno da se postigne donja ciljana granica, ne uzevši u obzir UGH' + ,ru: 'Избыток инсулина равного %1U, необходимого для достижения нижнего целевого значения, углеводы не приняты в расчет' ,sv: 'Överskott av insulin motsvarande %1U mer än nödvändigt för att nå lågt målvärde, kolhydrater ej medräknade' ,nb: 'Insulin tilsvarende %1U mer enn det trengs for å nå lavt mål, karbohydrater ikke medregnet' ,nl: 'Insulineoverschot van %1U om laag doel te behalen (excl. koolhydraten)' ,fi: 'Liikaa insuliinia: %1U enemmän kuin tarvitaan tavoitteeseen pääsyyn (huomioimatta hiilihydraatteja)' ,pt: 'Excesso de insulina equivalente a %1U além do necessário para atingir a meta inferior, sem levar em conta carboidratos' ,sk: 'Nadbytok inzulínu o %1U viac ako je potrebné na dosiahnutie spodnej cieľovej hranice. Neráta sa so sacharidmi.' + ,ko: '낮은 혈당 목표에 도달하기 위해 필요한 인슐린양보다 %1U의 인슐린 양이 초과 되었고 탄수화물 양이 초과되지 않았습니다.' ,it: 'L\'eccesso d\'insulina equivalente %1U più che necessari per raggiungere l\'obiettivo basso, non rappresentano i carboidrati.' - ,nl: 'Overschot insuline %1U meer dan nodig om het laag doel te bereiken, geen rekening gehouden met KH' + ,tr: 'Fazla insülin: Karbonhidratları dikkate alınmadan, alt hedefe ulaşmak için gerekenden %1U\'den daha fazla' //??? ,zh_cn: '胰岛素超过至血糖下限目标所需剂量%1单位,不计算碳水化合物' , pl: 'Nadmiar insuliny, %1J więcej niż potrzeba, aby osiągnąć cel dolnej granicy, nie biorąc pod uwagę węglowodanów' } @@ -10549,10 +11557,11 @@ function init() { cs:'Nadbytek inzulínu: o %1U více, než na dosažení spodní hranice cíle. UJISTĚTE SE, ŽE JE TO POKRYTO SACHARIDY' ,he: 'עודף אינסולין %1 נדרש כדי להגיע למטרה התחתונה. שים לב כי עליך לכסות אינסולין בגוף על ידי פחמימות ' ,de: 'Überschüssiges Insulin: %1U mehr als zum Erreichen der Untergrenze notwendig. SICHERSTELLEN, DASS IOB DURCH KOHLENHYDRATE ABGEDECKT WIRD' - ,dk: 'Overskud af insulin modsvarande %1U mere end nødvændigt for at nå lav målværdi, VÆR SIKKER PÅ AT IOB ER DÆKKET IND AF KULHYDRATER' + ,dk: 'Overskud af insulin modsvarande %1U mere end nødvendigt for at nå lav målværdi, VÆR SIKKER PÅ AT IOB ER DÆKKET IND AF KULHYDRATER' ,ro: 'Insulină în exces: %1U mai mult decât este necesar pentru a atinge ținta inferioară, ASIGURAȚI-VĂ CĂ INSULINA ESTE ACOPERITĂ DE CARBOHIDRAȚI' ,fr: 'Insuline en excès: %1U de plus que nécessaire pour atteindre la cible inférieure, ASSUREZ UN APPORT SUFFISANT DE GLUCIDES' ,bg: 'Излишният инсулин %1U е повече от необходимия за достигане до долната граница, ПРОВЕРИ ДАЛИ IOB СЕ ПОКРИВА ОТ ВЪГЛЕХИДРАТИТЕ' + ,hr: 'Višak inzulina je %1U više nego li je potrebno da se postigne donja ciljana granica, OBAVEZNO POKRIJTE SA UGH' ,ru: 'Избыток инсулина, равного %1U, необходимого для достижения нижнего целевого значения, ПОКРОЙТЕ IOB ИНСУЛИН В ОРГАНИЗМЕ УГЛЕВОДАМИ' ,sv: 'Överskott av insulin motsvarande %1U mer än nödvändigt för att nå lågt målvärde, SÄKERSTÄLL ATT IOB TÄCKS AV KOLHYDRATER' ,es: 'Exceso de insulina en %1U más de la necesaria para alcanzar objetivo inferior. ASEGÚRESE QUE LA INSULINA ACTIVA IOB ESTA CUBIERTA POR CARBOHIDRATOS' @@ -10561,7 +11570,9 @@ function init() { ,fi: 'Liikaa insuliinia: %1U enemmän kuin tarvitaan tavoitteeseen pääsyyn, VARMISTA RIITTÄVÄ HIILIHYDRAATTIEN SAANTI' ,pt: 'Excesso de insulina equivalente a %1U além do necessário para atingir a meta inferior. ASSEGURE-SE DE QUE A IOB ESTEJA COBERTA POR CARBOIDRATOS' ,sk: 'Nadbytok inzulínu o %1U viac ako je potrebné na dosiahnutie spodnej cieľovej hranice. UISTITE SA, ŽE JE TO POKRYTÉ SACHARIDMI' + ,ko: '낮은 혈당 목표에 도달하기 위해 필요한 인슐린양보다 %1U의 인슐린 양이 초과 되었습니다. 탄수화물로 IOB를 커버하세요.' ,it: 'L\'eccesso d\'insulina equivalente %1U più che necessario per raggiungere l\'obiettivo basso, ASSICURARSI IOB SIA COPERTO DA CARBOIDRATI' + ,tr: 'Fazla insülin: Alt KŞ hedefine ulaşmak için gerekenden %1 daha fazla insülin.IOB(Aktif İnsülin) Karbonhidrat tarafından karşılandığından emin olun.' ,zh_cn: '胰岛素超过至血糖下限目标所需剂量%1单位,确认IOB(活性胰岛素)被碳水化合物覆盖' ,pl: 'Nadmiar insuliny: o %1J więcej niż potrzeba, aby osiągnąć cel dolnej granicy. UPEWNIJ SIĘ, ŻE AKTYWNA INSULINA JEST POKRYTA WĘGLOWODANAMI' } @@ -10569,10 +11580,11 @@ function init() { cs:'Nutné snížení aktivního inzulínu o %1U k dosažení spodního cíle. Příliž mnoho bazálu?' ,he: 'צריך %1 פחות אינסולין כדי להגיע לגבול התחתון. האם הרמה הבזלית גבוהה מדי? ' ,de: 'Aktives Insulin um %1U reduzieren, um Untergrenze zu erreichen. Basal zu hoch?' - ,dk: '%1U reduktion nødventid i aktiv insulin for at nå lav værdi, for meget basal?' + ,dk: '%1U reduktion nødvendig i aktiv insulin for at nå lav mål, for meget basal?' ,ro: '%1U sunt în plus ca insulină activă pentru a atinge ținta inferioară, bazala este prea mare?' ,fr: 'Réduction d\'insuline active nécessaire pour atteindre la cible inférieure. Débit basal trop élevé ?' ,bg: '%1U намаляне е необходимо в активния инсулин до достигане до долната граница, прекалено висок базал?' + ,hr: 'Potrebno je smanjiti aktivni inzulin za %1U kako bi se dostigla donja ciljana granica, previše bazala? ' ,ru: 'Для достижения нижнего целевого значения необходимо понизить величину активного инсулина на %1U, велика база?' ,sv: '%1U minskning nödvändig i aktivt insulin för att nå lågt målvärde, för hög basal?' ,nb: '%1U reduksjon trengs i aktivt insulin for å nå lavt mål, for høy basal?' @@ -10581,7 +11593,9 @@ function init() { ,pt: 'Necessária redução de %1U na insulina ativa para atingir a meta inferior, excesso de basal?' ,nl: '%1U reductie vereist in actieve insuline om laag doel te bereiken, teveel basaal?' ,sk: 'Nutné zníženie aktívneho inzulínu o %1U pre dosiahnutie spodnej cieľovej hranice. Príliš veľa bazálu?' + ,ko: '낮은 혈당 목표에 도달하기 위해 활성 인슐린 양을 %1U 줄일 필요가 있습니다. basal양이 너무 많습니까?' ,it: 'Riduzione 1U% necessaria d\'insulina attiva per raggiungere l\'obiettivo basso, troppa basale?' + ,tr: 'Alt KŞ hedefi için %1U aktif insülin azaltılmalı, bazal oranı çok mu yüksek?' ,zh_cn: '活性胰岛素已可至血糖下限目标,需减少%1单位,基础率过高?' , pl: '%1J potrzebnej redukcji w aktywnej insulinie, aby osiągnąć niski cel dolnej granicy, Za duża dawka podstawowa ?' } @@ -10589,10 +11603,11 @@ function init() { cs:'úprava změnou bazálu není možná. Podat sacharidy?' ,he: 'השינוי ברמה הבזלית גדול מדי. תן פחמימות? ' ,de: 'Basalrate geht außerhalb des Zielbereiches. Kohlenhydrate nehmen?' - ,dk: 'basalændring uden for grænseværdi, gi kulhydrater?' + ,dk: 'basalændring uden for grænseværdi, giv kulhydrater?' ,ro: 'ajustarea bazalei duce în afara intervalului țintă. Suplimentați carbohirații?' ,fr: 'ajustement de débit basal hors de limites, prenez des glucides?' ,bg: 'Корекция на базала не е възможна, добавка на въглехидрати? ' + ,hr: 'prilagodba bazala je izvan raspona, dodati UGH?' ,ru: 'Корректировка базы вне диапазона, добавить углеводов?' ,sv: 'basaländring utanför gräns, ge kolhydrater?' ,es: 'ajuste basal fuera de rango, dar carbohidratos?' @@ -10600,8 +11615,10 @@ function init() { ,fi: 'säätö liian suuri, anna hiilihydraatteja?' ,pt: 'ajuste de basal fora da meta, dar carboidrato?' ,sk: 'úprava pomocou zmeny bazálu nie je možná. Podať sacharidy?' + ,ko: '적정 basal 양의 범위를 초과했습니다. 탄수화물 보충 하시겠습니까?' ,it: 'regolazione basale fuori campo, dare carboidrati?' ,nl: 'basaal aanpassing buiten bereik, geef KH?' + ,tr: 'Bazal oran ayarlaması limit dışı, karbonhidrat alınsın mı?' ,zh_cn: '基础率调整在范围之外,需要碳水化合物?' ,pl: 'dawka podstawowa poza zakresem, podać węglowodany?' } @@ -10609,10 +11626,11 @@ function init() { cs:'úprava změnou bazálu není možná. Podat bolus?' ,he: 'השינוי ברמה הבזלית גדול מדי. תן אינסולין? ' ,de: 'Anpassung der Basalrate außerhalb des Zielbereichs. Bolus abgeben?' - ,dk: 'basalændring udenfor grænseværdi, gi bolus?' + ,dk: 'basalændring udenfor grænseværdi, giv bolus?' ,ro: 'ajustarea bazalei duce în afara intervalului țintă. Suplimentați insulina?' ,fr: 'ajustement de débit basal hors de limites, prenez un bolus?' ,bg: 'Корекция на базала не е възможна, добавка на болус? ' + ,hr: 'prilagodna bazala je izvan raspona, dati bolus?' ,ru: 'Корректировка базы вне диапазона, добавить болюс?' ,sv: 'basaländring utanför gräns, ge bolus?' ,nb: 'basaljustering utenfor tillatt område, gi bolus?' @@ -10620,8 +11638,10 @@ function init() { ,es: 'Ajuste de basal fuera de rango, corregir con insulina?' ,pt: 'ajuste de basal fora da meta, dar bolus de correção?' ,sk: 'úprava pomocou zmeny bazálu nie je možná. Podať bolus?' + ,ko: '적정 basal 양의 범위를 초과했습니다. bolus를 추가하시겠습니까?' ,it: 'regolazione basale fuori campo, dare bolo?' ,nl: 'basaal aanpassing buiten bereik, bolus?' + ,tr: 'Bazal oran ayarlaması limit dışı, bolus alınsın mı?' ,zh_cn: '基础率调整在范围之外,需要大剂量?' ,pl: 'dawka podstawowa poza zakresem, podać insulinę?' } @@ -10629,19 +11649,22 @@ function init() { cs:'nad horním' ,he: 'מעל גבוה ' ,de: 'überhalb Obergrenze' - ,dk: 'over højt niveau' + ,dk: 'over højt grænse' ,ro: 'peste ținta superioară' ,fr: 'plus haut que la limite supérieure' - ,ru: 'Выше верхнего' + ,ru: 'Выше верхней границы' ,bg: 'над горната' + ,hr: 'iznad gornje granice' ,sv: 'över hög nivå' ,nb: 'over høy grense' ,fi: 'yli korkean' ,pt: 'acima do limite superior' ,sk: 'nad horným' ,es: 'por encima límite superior' + ,ko: '고혈당 초과' ,it: 'sopra alto' ,nl: 'boven hoog' + ,tr: 'üzerinde yüksek' ,zh_cn: '血糖过高' ,zh_tw: '血糖過高' ,pl: 'powyżej wysokiego' @@ -10650,19 +11673,22 @@ function init() { cs:'pod spodním' ,he: 'מתחת נמוך ' ,de: 'unterhalb Untergrenze' - ,dk: 'under lavt niveau' + ,dk: 'under lavt grænse' ,ro: 'sub ținta inferioară' ,fr: 'plus bas que la limite inférieure' ,bg: 'под долната' - ,ru: 'Ниже нижнего' + ,hr: 'ispod donje granice' + ,ru: 'Ниже нижней границы' ,sv: 'under låg nivå' ,nb: 'under lav grense' ,fi: 'alle matalan' ,es: 'por debajo límite inferior' ,pt: 'abaixo do limite inferior' ,sk: 'pod spodným' + ,ko: '저혈당 미만' ,it: 'sotto bassa' ,nl: 'onder laag' + ,tr: 'altında düşük' ,zh_cn: '血糖过低' ,zh_tw: '血糖過低' ,pl: 'poniżej niskiego' @@ -10675,15 +11701,18 @@ function init() { ,ro: 'Glicemie estimată %1 în țintă' ,fr: 'Glycémie cible projetée %1 ' ,bg: 'Предполагаемата КЗ %1 в граници' + ,hr: 'Procjena GUK %1 cilja' ,ru: 'Расчетная гликемия %1' ,sv: 'Önskat BS %1 mål' ,nb: 'Ønsket BS %1 mål' ,fi: 'Laskettu VS %1 tavoitteen' ,pt: 'Meta de glicemia estimada %1' ,sk: 'Predpokladaná glykémia %1 cieľ' + ,ko: '목표된 혈당 %1' ,it: 'Proiezione BG %1 obiettivo' ,es: 'Glucemia estimada %1 en objetivo' ,nl: 'Verwachte BG %1 doel' + ,tr: 'Beklenen KŞ %1 hedefi' ,zh_cn: '预计血糖%1目标' ,pl: 'Oczekiwany poziom glikemii %1' } @@ -10696,14 +11725,17 @@ function init() { ,ro: 'ținta este' ,ru: 'цель на' ,bg: 'цел към' + ,hr: 'ciljano' ,sv: 'önskad utgång' ,nb: 'sikter på' ,fi: 'tavoitellaan' ,es: 'resultado deseado' ,pt: 'meta' ,sk: 'cieľom' + ,ko: '목표' ,it: 'puntare a' ,nl: 'doel is' + ,tr: 'istenen sonuç' ,zh_cn: '目标在' ,pl: 'pożądany wynik' } @@ -10715,6 +11747,7 @@ function init() { ,ro: 'Bolus de %1 unități' ,fr: 'Bolus %1 unités' ,bg: 'Болус %1 единици' + ,hr: 'Bolus %1 jedinica' ,ru: 'Болюс %1 единиц' ,sv: 'Bolus %1 enheter' ,nb: 'Bolus %1 enheter' @@ -10722,8 +11755,10 @@ function init() { ,pt: 'Bolus %1 unidades' ,es: 'Bolus %1 unidades' ,sk: 'Bolus %1 jednotiek' + ,ko: 'Bolus %1 단위' ,it: 'Bolo %1 unità' ,nl: 'Bolus %1 eenheden' + ,tr: 'Bolus %1 Ünite' ,zh_cn: '大剂量%1单位' ,pl: 'Bolus %1 jednostek' } @@ -10735,6 +11770,7 @@ function init() { ,ro: 'sau ajustează bazala' ,fr: 'ou ajuster le débit basal' ,bg: 'или корекция на базала' + ,hr: 'ili prilagodba bazala' ,ru: 'или корректировать базу' ,sv: 'eller justera basal' ,nb: 'eller justere basal' @@ -10742,8 +11778,10 @@ function init() { ,pt: 'ou ajuste basal' ,es: 'o ajustar basal' ,sk: 'alebo úprava bazálu' + ,ko: '또는 조절 basal' ,it: 'o regolare basale' ,nl: 'of pas basaal aan' + ,tr: 'ya da bazal ayarlama' ,zh_cn: '或调整基础率' ,pl: 'lub dostosuj dawkę bazową' } @@ -10751,21 +11789,24 @@ function init() { cs:'Před korekcí zkontrolujte glukometrem glykémii!' ,he: 'מדוד רמת סוכר בדם באמצעות מד סוכר לפני תיקון ' ,de: 'Überprüfe deinen BZ mit dem Messgerät, bevor du eine Korrektur vornimmst!' - ,dk: 'Kontrol blodsukker med fingerprikker før der korrigeres!' + ,dk: 'Kontrollere blodsukker med fingerprikker / blodsukkermåler før der korrigeres!' ,ro: 'Verifică glicemia cu glucometrul înainte de a face o corecție!' ,fr: 'Vérifier la glycémie avec un glucomètre avant de corriger!' ,bg: 'Провери КЗ с глюкомер, преди кореция!' - ,ru: 'Перед корректировкой сверьте СК с глюкометром' + ,hr: 'Provjeri GUK glukometrom prije korekcije!' + ,ru: 'Перед корректировкой сверьте ГК с глюкометром' ,sv: 'Kontrollera blodglukos med fingerstick före korrigering!' ,nb: 'Sjekk blodsukker før korrigering!' ,fi: 'Tarkista VS mittarilla ennen korjaamista!' ,pt: 'Verifique glicemia de ponta de dedo antes de corrigir!' ,es: 'Verifique glucemia antes de corregir!' ,sk: 'Pred korekciou skontrolujte glykémiu glukometrom!' + ,ko: '수정하기 전에 혈당체크기를 사용하여 혈당을 체크하세요!' ,it: 'Controllare BG utilizzando glucometro prima di correggere!' ,nl: 'Controleer BG met bloeddruppel voor correctie!' ,zh_cn: '校正前请使用血糖仪测量血糖!' ,pl: 'Sprawdź glikemię z krwi przed podaniem korekty!' + ,tr: 'Düzeltme bolusu öncesi glikometreyle parmaktan KŞini kontrol edin!' } ,'Basal reduction to account %1 units:' : { cs:'Úprava bazálu pro náhradu bolusu %1 U ' @@ -10775,26 +11816,30 @@ function init() { ,ro: 'Reducere bazală pentru a compensa %1 unități:' ,fr: 'Réduction du débit basal pour obtenir l\'effet d\' %1 unité' ,bg: 'Намаляне на базала с %1 единици' - ,ru: 'Снижение базы на %1 единиц' + ,hr: 'Smanjeni bazal da uračuna %1 jedinica:' + ,ru: 'Снижение базы из-за %1 единиц болюса' ,sv: 'Basalsänkning för att nå %1 enheter' ,nb: 'Basalredusering for å nå %1 enheter' ,fi: 'Basaalin vähennys saadaksesi %1 yksikön vaikutuksen:' ,pt: 'Redução de basal para compensar %1 unidades:' ,es: 'Reducir basal para compesar %1 unidades:' ,sk: 'Úprava bazálu pre výpočet %1 jednotiek:' + ,ko: '%1 단위로 계산하기 위해 Basal 감소' ,it: 'Riduzione basale per conto %1 unità:' ,nl: 'Basaal verlaagd voor %1 eenheden' ,zh_cn: '基础率减少到%1单位' ,pl: 'Dawka bazowa zredukowana do 1% J' + ,tr: '%1 birimi telafi etmek için azaltılmış Bazaloranı:' } ,'30m temp basal' : { cs:'30ti minutový dočasný bazál' ,he: 'שלושים דקות רמה בזלית זמנית ' ,de: '30min temporäres Basal' - ,dk: '30 minuters temporer basal' + ,dk: '30 minuters midlertidig basal' ,ro: 'bazală temporară de 30 minute' ,fr: 'débit basal temporaire de 30 min' ,bg: '30м временен базал' + ,hr: '30m temp bazal' ,ru: '30 мин врем базал' ,sv: '30 minuters temporär basal' ,nb: '30 minutters midlertidig basal' @@ -10802,19 +11847,22 @@ function init() { ,pt: 'Basal temp 30m' ,es: '30 min. temporal basal' ,sk: '30 minutový dočasný bazál' + ,ko: '30분 임시 basal' ,it: '30m basale temp' ,nl: '30 minuten tijdelijke basaal' ,zh_cn: '30分钟临时基础率' ,pl: '30 minut tymczasowej dawki bazowej' + ,tr: '30 dk. geçici Bazal ' } ,'1h temp basal' : { cs:'hodinový dočasný bazál' ,he: 'שעה רמה בזלית זמנית ' ,de: '1h temporäres Basal' - ,dk: '1t temporer basal' + ,dk: '60 minutters midlertidig basal' ,ro: 'bazală temporară de 1 oră' ,fr: 'débit basal temporaire de 1 heure' ,bg: '1 час временен базал' + ,hr: '1h temp bazal' ,ru: '1 час врем базал' ,sv: '60 minuters temporär basal' ,nb: '60 minutters midlertidig basal' @@ -10822,132 +11870,152 @@ function init() { ,es: '1h temporal Basasl' ,pt: 'Basal temp 1h' ,sk: 'hodinový dočasný bazál' + ,ko: '1시간 임시 basal' ,it: '1h basale temp' ,nl: '1 uur tijdelijke basaal' ,zh_cn: '1小时临时基础率' ,pl: '1 godzina tymczasowej dawki bazowej' + ,tr: '1 sa. geçici bazal' } ,'Cannula change overdue!' : { cs:'Čas na výměnu set vypršel!' ,he: 'יש צורך בהחלפת קנולה! ' ,de: 'Kanülenwechsel überfällig!' - ,dk: 'Infusionsset, skift overskredet' + ,dk: 'Tid for skift af Infusionssæt overskredet!' ,ro: 'Depășire termen schimbare canulă!' ,bg: 'Времето за смяна на сет просрочено' + ,hr: 'Prošao rok za zamjenu kanile!' ,fr: 'Dépassement de date de changement de canule!' - ,ru: 'С' + ,ru: 'Срок замены катетера истек' ,sv: 'Infusionsset, bytestid överskriden' ,nb: 'Byttetid for infusjonssett overskredet' ,fi: 'Kanyylin ikä yli määräajan!' ,pt: 'Substituição de catéter vencida!' ,es: '¡Cambio de agujas vencido!' ,sk: 'Výmena kanyli po lehote!' + ,ko: '주입세트(cannula) 기한이 지났습니다. 변경하세요!' ,it: 'Cambio Cannula in ritardo!' ,nl: 'Cannule te oud!' ,zh_cn: '超过更换管路的时间' ,pl: 'Przekroczono czas wymiany wkłucia!' + ,tr: 'Kanül değişimi gecikmiş!' } ,'Time to change cannula' : { cs:'Čas na výměnu setu' ,he: 'הגיע הזמן להחליף קנולה ' ,de: 'Es ist Zeit, die Kanüle zu wechseln' - ,dk: 'Tid til at skifte infusionsset' + ,dk: 'Tid til at skifte infusionssæt' ,fr: 'Le moment est venu de changer de canule' ,ro: 'Este vremea să schimbați canula' ,bg: 'Време за смяна на сет' - ,ru: 'Пора заменить канюлю' + ,hr: 'Vrijeme za zamjenu kanile' + ,ru: 'Пора заменить катетер' ,sv: 'Dags att byta infusionsset' ,nb: 'På tide å bytte infusjonssett' ,fi: 'Aika vaihtaa kanyyli' ,es:' Hora sustituir cánula' ,pt: 'Hora de subistituir catéter' ,sk: 'Čas na výmenu kanyli' + ,ko: '주입세트(cannula)를 변경할 시간' ,it: 'Tempo di cambiare la cannula' ,nl: 'Verwissel canule' ,zh_cn: '已到更换管路的时间' ,pl: 'Czas do wymiany wkłucia' + ,tr: 'Kanül değiştirme zamanı' } ,'Change cannula soon' : { cs:'Blíží se čas na výměnu setu' ,he: 'החלף קנולה בקרוב ' ,de: 'Kanüle bald wechseln' - ,dk: 'Skift infusionsset snart' + ,dk: 'Skift infusionssæt snart' ,ro: 'Schimbați canula în curând' ,fr: 'Changement de canule bientòt' ,bg: 'Смени сета скоро' - ,ru: 'Подходит время замены канюли' + ,hr: 'Zamijena kanile uskoro' + ,ru: 'Приближается время замены катетера' ,sv: 'Byt infusionsset snart' ,nb: 'Bytt infusjonssett snart' ,fi: 'Vaihda kanyyli pian' ,pt: 'Substituir catéter em breve' ,sk: 'Čoskoro bude potrebné vymeniť kanylu' + ,ko: '주입세트(cannula)를 곧 변경하세요.' ,it: 'Cambio cannula prossimamente' ,nl: 'Verwissel canule binnenkort' ,zh_cn: '接近更换管路的时间' ,pl: 'Wkrótce wymiana wkłucia' + ,tr: 'Yakında kanül değiştirin' } ,'Cannula age %1 hours' : { cs:'Stáří setu %1 hodin' ,he: 'כיל הקנולה %1 שעות ' ,de: 'Kanülen Alter %1 Stunden' - ,dk: 'Infusionsset tid %1 timer' + ,dk: 'Infusionssæt tid %1 timer' ,fr: 'âge de la canule %1 heures' ,ro: 'Vechimea canulei în ore: %1' ,bg: 'Сетът е на %1 часове' - ,ru: 'Возраст канюли %1 час' + ,hr: 'Staros kanile %1 sati' + ,ru: 'Катетер отработал %1 час' ,sv: 'Infusionsset tid %1 timmar' ,nb: 'infusjonssett alder %1 timer' ,fi: 'Kanyylin ikä %1 tuntia' ,pt: 'Idade do catéter %1 horas' ,es: 'Cánula usada %1 horas' ,sk: 'Vek kanyli %1 hodín' + ,ko: '주입세트(cannula) %1시간 사용' ,it: 'Durata Cannula %1 ore' ,nl: 'Canule leeftijd %1 uren' ,zh_cn: '管路已使用%1小时' ,pl: 'Czas od wymiany wkłucia %1 godzin' + ,tr: 'Kanül yaşı %1 saat' } ,'Inserted' : { cs:'Nasazený' ,he: 'הוכנס ' ,de: 'Eingesetzt' - ,dk: 'Indsat' + ,dk: 'Isat' ,ro: 'Inserat' ,fr: 'Insérée' ,bg: 'Поставен' - ,ru: 'Введено' + ,hr: 'Postavljanje' + ,ru: 'Установлен' ,sv: 'Applicerad' ,nb: 'Satt inn' ,fi: 'Asetettu' - ,es: 'Insertar' + ,es: 'Insertado' ,pt: 'Inserido' ,sk: 'Zavedený' + ,ko: '삽입된' ,it: 'Inserito' ,nl: 'Ingezet' ,zh_cn: '已植入' ,zh_tw: '已植入' ,pl: 'Zamontowano' + ,tr: 'Yerleştirilmiş' } ,'CAGE' : { cs:'SET' ,he: 'גיל הקנולה ' ,de: 'CAGE' - ,dk: 'CAGE' + ,dk: 'Indstik alder' ,ro: 'VC' ,bg: 'ВС' + ,hr: 'Starost kanile' ,fr: 'CAGE' - ,ru: 'ВКан' + ,ru: 'ОтрабКат' ,sv: 'Nål' ,nb: 'Nål alder' ,fi: 'KIKÄ' ,pt: 'ICAT' - ,es: 'Carb.desde' + ,es: 'Cánula desde' ,sk: 'SET' + ,ko: '주입세트사용기간' ,it: 'CAGE' ,nl: 'CAGE' ,zh_cn: '管路' ,zh_tw: '管路' ,pl: 'Wiek wkłucia' + ,tr: 'CAGE' } ,'COB' : { cs:'SACH' @@ -10956,18 +12024,21 @@ function init() { ,dk: 'COB' ,ro: 'COB' ,bg: 'АВХ' + ,hr: 'Aktivni UGH' ,fr: 'COB' - ,ru: 'Активн углеводы' + ,ru: 'Активн углеводы COB' ,sv: 'COB' - ,nb: 'Aktive katbohydrater' + ,nb: 'Aktive karbohydrater' ,fi: 'AH' ,pt: 'COB' - ,es: 'Carbohidratos activos' + ,es: 'Carb. activos' ,sk: 'SACH' + ,ko: 'COB' ,it: 'COB' ,nl: 'COB' ,zh_cn: '活性碳水COB' ,pl: 'Aktywne węglowodany' + ,tr: 'COB' } ,'Last Carbs' : { cs:'Poslední sacharidy' @@ -10977,6 +12048,7 @@ function init() { ,ro: 'Ultimii carbohidrați' ,fr: 'Derniers glucides' ,bg: 'Последни ВХ' + ,hr: 'Posljednji UGH' ,ru: 'Новые углеводы' ,sv: 'Senaste kolhydrater' ,nb: 'Siste karbohydrater' @@ -10984,10 +12056,12 @@ function init() { ,pt: 'Último carboidrato' ,es: 'último carbohidrato' ,sk: 'Posledné sacharidy' + ,ko: '마지막 탄수화물' ,it: 'Ultimi carboidrati' ,nl: 'Laatse KH' ,zh_cn: '上次碳水' ,pl: 'Ostatnie węglowodany' + ,tr: 'Son Karbonhidrat' } ,'IAGE' : { cs:'INZ' @@ -10996,39 +12070,45 @@ function init() { ,dk: 'Insulinalder' ,ro: 'VI' ,fr: 'IAGE' - ,bg: 'ВИнс' - ,ru: 'ВозрИнс' + ,bg: 'ИнсСрок' + ,hr: 'Starost inzulina' + ,ru: 'ИнсСрок' ,sv: 'Insulinålder' ,nb: 'Insulinalder' ,fi: 'IIKÄ' ,pt: 'IddI' ,es: 'Insul.desde' ,sk: 'INZ' + ,ko: '인슐린사용기간' ,it: 'IAGE' ,nl: 'IAGE' ,zh_cn: '胰岛素' ,zh_tw: '胰島素' ,pl: 'Wiek insuliny' + ,tr: 'IAGE' } ,'Insulin reservoir change overdue!' : { cs:'Čas na výměnu zásobníku vypršel!' ,he: 'החלף מאגר אינסולין! ' ,de: 'Ampullenwechsel überfällig!' - ,dk: 'Insulinbyttetid overskredet!' + ,dk: 'Tid for skift af insulinreservoir overskredet!' ,fr: 'Dépassement de date de changement de réservoir d\'insuline!' ,ro: 'Termenul de schimbare a rezervorului de insulină a fost depășit' ,bg: 'Смянатата на резервоара просрочена' - ,ru: 'Срок замены тубы инсулина истек' + ,hr: 'Prošao rok za zamjenu spremnika!' + ,ru: 'Срок замены резервуара инсулина истек' ,sv: 'Insulinbytestid överskriden' - ,nb: 'Insulinbytestid overskrevet' + ,nb: 'Insulinbyttetid overskrevet' ,fi: 'Insuliinisäiliö vanhempi kuin määräaika!' ,pt: 'Substituição de reservatório vencida!' ,es: 'Excedido plazo del cambio depósito de insulina!' ,sk: 'Čas na výmenu inzulínu po lehote!' + ,ko: '레저보(펌프 주사기)의 사용기한이 지났습니다. 변경하세요!' ,it: 'Cambio serbatoio d\'insulina in ritardo!' ,nl: 'Verwissel insulinereservoir nu!' ,zh_cn: '超过更换胰岛素储液器的时间' ,pl: 'Przekroczono czas wymiany zbiornika na insulinę!' + ,tr: 'İnsülin rezervuarı değişimi gecikmiş!' } ,'Time to change insulin reservoir' : { cs:'Čas na výměnu zásobníku' @@ -11038,17 +12118,20 @@ function init() { ,ro: 'Este timpul pentru schimbarea rezervorului de insulină' ,fr: 'Le moment est venu de changer de réservoir d\'insuline' ,bg: 'Време е за смяна на резервоара' - ,ru: 'Наступил срок замены тубы инсулина' + ,hr: 'Vrijeme za zamjenu spremnika' + ,ru: 'Наступил срок замены резервуара инсулина' ,sv: 'Dags att byta insulinreservoar' ,nb: 'På tide å bytte insulinreservoar' ,fi: 'Aika vaihtaa insuliinisäiliö' ,pt: 'Hora de substituir reservatório' ,es: 'Hora de sustituir depósito insulina' ,sk: 'Čas na výmenu inzulínu' + ,ko: '레저보(펌프 주사기)를 변경할 시간' ,it: 'Momento di cambiare serbatoio d\'insulina' ,nl: 'Verwissel insuline reservoir' ,zh_cn: '已到更换胰岛素储液器的时间' ,pl: 'Czas do zmiany zbiornika na insulinę!' + ,tr: 'İnsülin rezervuarını değiştirme zamanı!' } ,'Change insulin reservoir soon' : { cs:'Blíží se čas na výměnu zásobníku' @@ -11058,17 +12141,20 @@ function init() { ,ro: 'Rezervorul de insulină trebuie schimbat în curând' ,fr: 'Changement de réservoir d\'insuline bientôt' ,bg: 'Смени резервоара скоро' - ,ru: 'Наступает срок замены тубы инсулина' + ,hr: 'Zamjena spremnika uskoro' + ,ru: 'Наступает срок замены резервуара инсулина' ,sv: 'Byt insulinreservoar snart' ,nb: 'Bytt insulinreservoar snart' ,fi: 'Vaihda insuliinisäiliö pian' ,pt: 'Substituir reservatório em brave' ,es: 'Sustituir depósito insulina en breve' ,sk: 'Čoskoro bude potrebné vymeniť inzulín' + ,ko: '레저보(펌프 주사기)안의 인슐린을 곧 변경하세요.' ,it: 'Cambiare serbatoio d\'insulina prossimamente' ,nl: 'Verwissel insuline reservoir binnenkort' ,zh_cn: '接近更换胰岛素储液器的时间' ,pl: 'Wkrótce wymiana zbiornika na insulinę!' + ,tr: 'Yakında insülin rezervuarını değiştirin' } ,'Insulin reservoir age %1 hours' : { cs:'Stáří zásobníku %1 hodin' @@ -11079,16 +12165,19 @@ function init() { ,ro: 'Vârsta reservorului de insulină %1 ore' ,ru: 'Картридж инсулина отработал %1часов' ,bg: 'Резервоарът е на %1 часа' + ,hr: 'Spremnik zamijenjen prije %1 sati' ,sv: 'Insulinreservoarsålder %1 timmar' ,nb: 'Insulinreservoaralder %1 timer' ,fi: 'Insuliinisäiliön ikä %1 tuntia' ,pt: 'Idade do reservatório %1 horas' ,sk: 'Vek inzulínu %1 hodín' ,es: 'Depósito insulina desde %1 horas' + ,ko: '레저보(펌프 주사기) %1시간 사용' ,it: 'IAGE - Durata Serbatoio d\'insulina %1 ore' ,nl: 'Insuline reservoir leeftijd %1 uren' ,zh_cn: '胰岛素储液器已使用%1小时' ,pl: 'Wiek zbiornika na insulinę %1 godzin' + ,tr: 'İnsülin rezervuar yaşı %1 saat' } ,'Changed' : { cs:'Vyměněno' @@ -11099,16 +12188,19 @@ function init() { ,ro: 'Schimbat' ,ru: 'Замена произведена' ,bg: 'Сменен' + ,hr: 'Promijenjeno' ,sv: 'Bytt' ,nb: 'Byttet' ,fi: 'Vaihdettu' ,pt: 'Substituído' ,es: 'Cambiado' ,sk: 'Vymenený' + ,ko: '변경됨' ,it: 'Cambiato' ,nl: 'veranderd' ,zh_cn: '已更换' ,pl: 'Wymieniono' + ,tr: 'Değişmiş' } ,'IOB' : { cs:'IOB' @@ -11116,19 +12208,22 @@ function init() { ,de: 'IOB' ,dk: 'IOB' ,ro: 'IOB' - ,ru: 'Активный Инсулин' + ,ru: 'Активный Инсулин IOB' ,fr: 'IOB' ,bg: 'АИ' + ,hr: 'Aktivni inzulin' ,sv: 'IOB' ,nb: 'Aktivt insulin' ,es: 'Insulina Activa IOB' ,fi: 'IOB' ,pt: 'IOB' ,sk: 'IOB' + ,ko: 'IOB' ,it: 'IOB' ,nl: 'IOB' ,zh_cn: '活性胰岛素IOB' ,pl: 'Aktywna insulina' + ,tr: 'IOB' } ,'Careportal IOB' : { cs:'IOB z ošetření' @@ -11139,16 +12234,19 @@ function init() { ,ru: 'Активн Инс на портале назначений' ,fr: 'Careportal IOB' ,bg: 'АИ от Кеърпортал' + ,hr: 'Careportal IOB' ,sv: 'IOB i Careportal' ,nb: 'Aktivt insulin i Careportal' ,fi: 'Careportal IOB' ,es: 'Insulina activa en Careportal' ,pt: 'IOB do Careportal' ,sk: 'IOB z portálu starostlivosti' + ,ko: '케어포털 IOB' ,it: 'IOB Somministrazioni' ,nl: 'Careportal IOB' ,zh_cn: '服务面板IOB(活性胰岛素)' ,pl: 'Aktywna insulina z portalu' + ,tr: 'Careportal IOB (Aktif İnsülin)' } ,'Last Bolus' : { cs:'Poslední bolus' @@ -11159,16 +12257,19 @@ function init() { ,ro: 'Ultimul bolus' ,ru: 'Прошлый болюс' ,bg: 'Последен болус' + ,hr: 'Prethodni bolus' ,sv: 'Senaste Bolus' ,nb: 'Siste Bolus' ,fi: 'Viimeisin bolus' ,pt: 'Último bolus' ,es: 'Último bolo' ,sk: 'Posledný bolus' + ,ko: '마지막 Bolus' ,it: 'Ultimo bolo' ,nl: 'Laatste bolus' ,zh_cn: '上次大剂量' ,pl: 'Ostatni bolus' + ,tr: 'Son Bolus' } ,'Basal IOB' : { cs:'IOB z bazálů' @@ -11176,19 +12277,22 @@ function init() { ,de: 'Basal IOB' ,dk: 'Basal IOB' ,ro: 'IOB bazală' - ,ru: 'Активн Базал' + ,ru: 'Активн Базал IOB' ,fr: 'IOB du débit basal' ,bg: 'Базален АИ' + ,hr: 'Bazalni aktivni inzulin' ,sv: 'Basal IOB' ,nb: 'Basal Aktivt Insulin' ,fi: 'Basaalin IOB' ,pt: 'IOB basal' ,es: 'Basal Insulina activa' ,sk: 'Bazálny IOB' + ,ko: 'Basal IOB' ,it: 'Basale IOB' ,nl: 'Basaal IOB' ,zh_cn: '基础率IOB(活性胰岛素)' ,pl: 'Aktywna insulina z dawki bazowej' + ,tr: 'Bazal IOB' } ,'Source' : { cs:'Zdroj' @@ -11199,36 +12303,42 @@ function init() { ,fr: 'Source' ,ru: 'Источник' ,bg: 'Източник' + ,hr: 'Izvor' ,sv: 'Källa' ,nb: 'Kilde' ,fi: 'Lähde' ,pt: 'Fonte' ,es: 'Fuente' ,sk: 'Zdroj' + ,ko: '출처' ,it: 'Fonte' ,nl: 'bron' ,zh_cn: '来源' ,pl: 'Źródło' + ,tr: 'Kaynak' } ,'Stale data, check rig?' : { cs:'Zastaralá data, zkontrolovat mobil?' ,he: 'מידע ישן, בדוק את המערכת? ' ,de: 'Daten sind veraltet, Übertragungsgerät prüfen?' - ,dk: 'Gammel data, kontrol uploader?' + ,dk: 'Gammel data, kontrollere uploader?' ,ro: 'Date învechite, verificați uploaderul!' ,fr: 'Valeurs trop anciennes, vérifier l\'uploadeur' ,ru: 'Устаревшие данные, проверьте загрузчик' ,bg: 'Стари данни, провери телефона' + ,hr: 'Nedostaju podaci, provjera opreme?' ,es: 'Datos desactualizados, controlar la subida?' ,sv: 'Gammal data, kontrollera rigg?' ,nb: 'Gamle data, sjekk rigg?' ,fi: 'Tiedot vanhoja, tarkista lähetin?' ,pt: 'Dados antigos, verificar uploader?' ,sk: 'Zastaralé dáta, skontrolujte uploader' + ,ko: '오래된 데이터입니다. 확인해 보시겠습니까?' ,it: 'dati non aggiornati, controllare il telefono?' ,nl: 'Geen data, controleer uploader' ,zh_cn: '数据过期,检查一下设备?' , pl: 'Dane są nieaktualne, sprawdź urządzenie transmisyjne.' + ,tr: 'Veri güncel değil, vericiyi kontrol et?' } ,'Last received:' : { cs:'Naposledy přijato:' @@ -11239,16 +12349,19 @@ function init() { ,ro: 'Ultimile date:' ,ru: 'Предыдущий полученный' ,bg: 'Последно получени' + ,hr: 'Zadnji podaci od:' ,sv: 'Senast mottagen:' ,nb: 'Sist mottatt:' ,fi: 'Viimeksi vastaanotettu:' ,pt: 'Último recebido:' ,es: 'Último recibido:' ,sk: 'Naposledy prijaté:' + ,ko: '마지막 수신' ,it: 'Ultime ricevute:' ,nl: 'laatste ontvangen' ,zh_cn: '上次接收:' ,pl: 'Ostatnio odebrane:' + ,tr: 'Son alınan:' } ,'%1m ago' : { cs:'%1m zpět' @@ -11259,16 +12372,19 @@ function init() { ,fr: 'il y a %1 min' ,ru: 'мин назад' ,bg: 'преди %1 мин.' + ,hr: 'prije %1m' ,sv: '%1m sedan' ,nb: '%1m siden' ,fi: '%1m sitten' ,es: '%1min. atrás' ,pt: '%1m atrás' ,sk: 'pred %1m' + ,ko: '%1분 전' ,it: '%1m fa' ,nl: '%1m geleden' ,zh_cn: '%1分钟前' ,pl: '%1 minut temu' + ,tr: '%1 dk. önce' } ,'%1h ago' : { cs:'%1h zpět' @@ -11279,16 +12395,19 @@ function init() { ,fr: '%1 heures plus tôt' ,ru: 'час назад' ,bg: 'преди %1 час' + ,hr: 'prije %1 sati' ,sv: '%1h sedan' ,nb: '%1h siden' ,fi: '%1h sitten' ,pt: '%1h atrás' ,es: '%1h. atrás' ,sk: 'pred %1h' + ,ko: '%1시간 전' ,it: '%1h fa' ,nl: '%1u geleden' ,zh_cn: '%1小时前' ,pl: '%1 godzin temu' + ,tr: '%1 sa. önce' } ,'%1d ago' : { cs:'%1d zpět' @@ -11299,16 +12418,19 @@ function init() { ,fr: '%1 jours plus tôt' ,ru: 'дн назад' ,bg: 'преди %1 ден' + ,hr: 'prije %1 dana' ,sv: '%1d sedan' ,nb: '%1d siden' ,fi: '%1d sitten' ,pt: '%1d atrás' ,es: '%1d atrás' ,sk: 'pred %1d' + ,ko: '%1일 전' ,it: '%1d fa' ,nl: '%1d geleden' ,zh_cn: '%1天前' ,pl: '%1 dni temu' + ,tr: '%1 gün önce' } ,'RETRO' : { cs:'RETRO' @@ -11316,8 +12438,9 @@ function init() { ,de: 'RETRO' ,dk: 'RETRO' ,ro: 'VECHI' - ,ru: 'РЕТРО' + ,ru: 'ПРОШЛОЕ' ,bg: 'РЕТРО' + ,hr: 'RETRO' ,fr: 'RETRO' ,sv: 'RETRO' ,nb: 'GAMMELT' @@ -11325,10 +12448,12 @@ function init() { ,pt: 'RETRO' ,es: 'RETRO' ,sk: 'RETRO' + ,ko: 'RETRO' ,it: 'RETRO' ,nl: 'RETRO' ,zh_cn: '历史数据' ,pl: 'RETRO' + ,tr: 'RETRO Geçmiş' } ,'SAGE' : { cs:'SENZ' @@ -11339,37 +12464,43 @@ function init() { ,ru: 'Сенсор проработал' ,fr: 'SAGE' ,bg: 'ВС' + ,hr: 'Starost senzora' ,sv: 'Sensor' ,nb: 'Sensoralder' ,fi: 'SIKÄ' ,pt: 'IddS' ,sk: 'SENZ' ,es: 'Sensor desde' + ,ko: '센서사용기간' ,it: 'SAGE' ,nl: 'SAGE' ,zh_cn: '探头' ,zh_tw: '探頭' ,pl: 'Wiek sensora' + ,tr: 'SAGE' } ,'Sensor change/restart overdue!' : { cs:'Čas na výměnu senzoru vypršel!' ,he: 'שנה או אתחל את הסנסור! ' ,de: 'Sensorwechsel/-neustart überfällig!' - ,dk: 'Sensor bytte/genstart overskredet!' + ,dk: 'Sensor skift/genstart overskredet!' ,ro: 'Depășire termen schimbare/restart senzor!' ,fr: 'Changement/Redémarrage du senseur dépassé!' - ,ru: 'Рестарт сенсора просрочен' + ,ru: 'Рестарт сенсора пропущен' ,bg: 'Смяната/рестартът на сензора са пресрочени' + ,hr: 'Prošao rok za zamjenu/restart senzora!' ,sv: 'Sensor byte/omstart överskriden!' ,nb: 'Sensor bytte/omstart overskredet!' ,fi: 'Sensorin vaihto/uudelleenkäynnistys yli määräajan!' ,pt: 'Substituição/reinício de sensor vencido' ,es: 'Sustituir/reiniciar, sensor vencido' ,sk: 'Čas na výmenu/reštart sensoru uplynul!' + ,ko: '센서 사용기한이 지났습니다. 센서를 교체/재시작 하세요!' ,it: 'Cambio/riavvio del sensore in ritardo!' ,nl: 'Sensor vevang/hertstart tijd gepasseerd' ,zh_cn: '超过更换/重启探头的时间' ,pl: 'Przekroczono czas wymiany/restartu sensora!' + ,tr: 'Sensör değişimi/yeniden başlatma gecikti!' } ,'Time to change/restart sensor' : { cs:'Čas na výměnu senzoru' @@ -11380,36 +12511,42 @@ function init() { ,ru: 'Время замены/рестарта сенсора' ,fr: 'C\'est le moment de changer/redémarrer le senseur' ,bg: 'Време за смяна/рестарт на сензора' + ,hr: 'Vrijeme za zamjenu/restart senzora' ,sv: 'Dags att byta/starta om sensorn' ,nb: 'På tide å bytte/restarte sensoren' ,fi: 'Aika vaihtaa / käynnistää sensori uudelleen' ,pt: 'Hora de substituir/reiniciar sensor' ,es: 'Hora de sustituir/reiniciar sensor' ,sk: 'Čas na výmenu/reštart senzoru' + ,ko: '센서 교체/재시작 시간' ,it: 'Tempo di cambiare/riavvio sensore' ,nl: 'Sensor vervangen of herstarten' ,zh_cn: '已到更换/重启探头的时间' ,pl: 'Czas do wymiany/restartu sensora' + ,tr: 'Sensörü değiştirme/yeniden başlatma zamanı' } ,'Change/restart sensor soon' : { cs:'Blíží se čas na výměnu senzoru' ,he: 'שנה או אתחל את הסנסור בקרוב ' ,de: 'Sensor bald wechseln/neustarten' - ,dk: 'Byt/genstart sensor snart' + ,dk: 'Skift eller genstart sensor snart' ,ro: 'Schimbați/restartați senzorul în curând' ,fr: 'Changement/Redémarrage du senseur bientôt' ,ru: 'Приближается срок замены/рестарта сенсора' ,bg: 'Смени/рестартирай сензора скоро' + ,hr: 'Zamijena/restart senzora uskoro' ,sv: 'Byt/starta om sensorn snart' ,nb: 'Bytt/restarta sensoren snart' ,fi: 'Vaihda/käynnistä sensori uudelleen pian' ,pt: 'Mudar/reiniciar sensor em breve' ,es: 'Cambiar/Reiniciar sensor en breve' ,sk: 'Čoskoro bude potrebné vymeniť/reštartovať senzor' + ,ko: '센서를 곧 교체/재시작 하세요' ,it: 'Modifica/riavvio sensore prossimamente' ,nl: 'Herstart of vervang sensor binnenkort' ,zh_cn: '接近更换/重启探头的时间' ,pl: 'Wkrótce czas wymiany/restartu sensora' + ,tr: 'Sensörü yakında değiştir/yeniden başlat' } ,'Sensor age %1 days %2 hours' : { cs:'Stáří senzoru %1 dní %2 hodin' @@ -11418,58 +12555,67 @@ function init() { ,dk: 'Sensoralder %1 dage %2 timer' ,ro: 'Senzori vechi de %1 zile și %2 ore' ,fr: 'Âge su senseur %1 jours et %2 heures' - ,ru: 'Сенсор проработал % дн % час' + ,ru: 'Сенсор отработал % дн % час' ,bg: 'Сензорът е на %1 дни %2 часа ' + ,hr: 'Starost senzora %1 dana i %2 sati' ,sv: 'Sensorålder %1 dagar %2 timmar' ,nb: 'Sensoralder %1 dag %2 timer' ,fi: 'Sensorin ikä %1 päivää, %2 tuntia' ,pt: 'Idade do sensor %1 dias %2 horas' ,es: 'Sensor desde %1 días %2 horas' ,sk: 'Vek senzoru %1 dní %2 hodín' + ,ko: '센서사용기간 %1일 %2시간' ,it: 'Durata Sensore %1 giorni %2 ore' ,nl: 'Sensor leeftijd %1 dag(en) en %2 uur' ,zh_cn: '探头使用了%1天%2小时' ,pl: 'Wiek sensora: %1 dni %2 godzin' + ,tr: 'Sensör yaşı %1 gün %2 saat' } ,'Sensor Insert' : { cs: 'Výměna sensoru' ,he: 'הכנס סנסור ' ,de: 'Sensor eingesetzt' - ,dk: 'Sensor indsæt' + ,dk: 'Sensor isat' ,ro: 'Inserția senzorului' ,fr: 'Insertion du senseur' ,ru: 'Установка сенсора' ,bg: 'Поставяне на сензора' + ,hr: 'Postavljanje senzora' ,sv: 'Sensor insättning' ,nb: 'Sensor satt inn' ,fi: 'Sensorin Vaihto' ,es: 'Insertar sensor' ,pt: 'Inserção de sensor' ,sk: 'Výmena senzoru' + ,ko: '센서삽입' ,it: 'SAGE - inserimento sensore' ,nl: 'Sensor ingebracht' ,zh_cn: '植入探头' ,pl: 'Zamontuj sensor' + ,tr: 'Sensor yerleştirme' } ,'Sensor Start' : { cs: 'Znovuspuštění sensoru' ,he: 'סנסור התחיל ' ,de: 'Sensorstart' - ,dk: 'Sensorstart' + ,dk: 'Sensor start' ,ro: 'Pornirea senzorului' - ,ru: 'Запуск сенсора' + ,ru: 'Старт сенсора' ,fr: 'Démarrage du senseur' ,bg: 'Стартиране на сензора' + ,hr: 'Pokretanje senzora' ,sv: 'Sensorstart' ,nb: 'Sensorstart' ,fi: 'Sensorin Aloitus' ,pt: 'Início de sensor' ,es: 'Inicio del sensor' ,sk: 'Štart senzoru' + ,ko: '센서시작' ,it: 'SAGE - partenza sensore' ,nl: 'Sensor start' ,zh_cn: '启动探头' ,pl: 'Uruchom sensor' + ,tr: 'Sensör başlatma' } ,'days' : { cs: 'dní' @@ -11480,32 +12626,39 @@ function init() { ,fr: 'jours' ,ru: 'дн' ,bg: 'дни' + ,hr: 'dana' ,sv: 'dagar' ,nb: 'dager' ,fi: 'päivää' ,pt: 'dias' ,es: 'días' ,sk: 'dní' + ,ko: '일' ,it: 'giorni' ,nl: 'dagen' ,zh_cn: '天' ,pl: 'dni' + ,tr: 'Gün' } ,'Insulin distribution' : { cs: 'Rozložení inzulínu' ,he: 'התפלגות אינסולין ' ,de: 'Insulinverteilung' - ,dk: 'Insulindistribution' + ,dk: 'Insulinfordeling' ,ro: 'Distribuția de insulină' ,fr: 'Distribution de l\'insuline' ,ru: 'распределение инсулина' ,fi: 'Insuliinijakauma' ,sv: 'Insulindistribution' + ,ko: '인슐린주입' ,it: 'Distribuzione di insulina' ,es: 'Distribución de la insulina' ,nl: 'Insuline verdeling' + ,zh_cn: '胰岛素分布' ,bg: 'разпределение на инсулина' + ,hr: 'Raspodjela inzulina' ,pl: 'podawanie insuliny' + ,tr: 'İnsülin dağılımı' } ,'To see this report, press SHOW while in this view' : { cs: 'Pro zobrazení toho výkazu stiskněte Zobraz na této záložce' @@ -11516,12 +12669,16 @@ function init() { ,ro: 'Pentru a vedea acest raport, apăsați butonul SHOW' ,ru: 'чтобы увидеть отчет, нажмите show/показать' ,fi: 'Nähdäksesi tämän raportin, paina NÄYTÄ tässä näkymässä' + ,ko: '이 보고서를 보려면 "확인"을 누르세요' ,it: 'Per guardare questo report, premere SHOW all\'interno della finestra' ,es: 'Presione SHOW para mostrar el informe en esta vista' ,nl: 'Om dit rapport te zien, druk op "Laat zien"' + ,zh_cn: '要查看此报告,请在此视图中按生成' ,sv: 'För att se denna rapport, klicka på "Visa"' ,bg: 'За да видите тази статистика, натиснете ПОКАЖИ' + ,hr: 'Za prikaz ovog izvješća, pritisnite PRIKAŽI na ovom prozoru' , pl: 'Aby wyświetlić ten raport, naciśnij przycisk POKAŻ w tym widoku' + ,tr: 'Bu raporu görmek için bu görünümde GÖSTER düğmesine basın.' } ,'AR2 Forecast' : { cs: 'AR2 predikci' @@ -11533,11 +12690,15 @@ function init() { ,ru: 'прогноз AR2' ,es: 'Pronóstico AR2' ,fi: 'AR2 Ennusteet' + ,ko: 'AR2 예측' ,it: 'Previsione AR2' ,nl: 'AR2 Voorspelling' + ,zh_cn: 'AR2 预测' ,sv: 'AR2 Förutsägelse' ,bg: 'AR2 прогнози' + ,hr: 'AR2 procjena' ,pl: 'Prognoza AR2' + ,tr: 'AR2 Tahmini' } ,'OpenAPS Forecasts' : { cs: 'OpenAPS predikci' @@ -11549,11 +12710,15 @@ function init() { ,ru: 'прогнозы OpenAPS' ,es: 'Pronóstico OpenAPS' ,fi: 'OpenAPS Ennusteet' + ,ko: 'OpenAPS 예측' ,it: 'Previsione OpenAPS' ,nl: 'OpenAPS Voorspelling' + ,zh_cn: 'OpenAPS 预测' ,sv: 'OpenAPS Förutsägelse' ,bg: 'OpenAPS прогнози' + ,hr: 'OpenAPS prognoze' ,pl: 'Prognoza OpenAPS' + ,tr: 'OpenAPS Tahminleri' } ,'Temporary Target' : { cs: 'Dočasný cíl glykémie' @@ -11565,11 +12730,15 @@ function init() { ,ru: 'промежуточная цель' ,fi: 'Tilapäinen tavoite' ,es: 'Objetivo temporal' + ,ko: '임시목표' ,it: 'Obbiettivo temporaneo' ,nl: 'Tijdelijk doel' + ,zh_cn: '临时目标' ,sv: 'Tillfälligt mål' ,bg: 'временна цел' + ,hr: 'Privremeni cilj' ,pl: 'Cel tymczasowy' + ,tr: 'Geçici Hedef' } ,'Temporary Target Cancel' : { cs: 'Dočasný cíl glykémie konec' @@ -11581,11 +12750,15 @@ function init() { ,ru: 'отмена промежуточной цели' ,fi: 'Peruuta tilapäinen tavoite' ,es: 'Objetivo temporal cancelado' + ,ko: '임시목표취소' ,it: 'Obbiettivo temporaneo cancellato' ,nl: 'Annuleer tijdelijk doel' + ,zh_cn: '临时目标取消' ,sv: 'Avsluta tillfälligt mål' ,bg: 'Отмяна на временна цел' + ,hr: 'Otkaz privremenog cilja' ,pl: 'Zel tymczasowy anulowany' + ,tr: 'Geçici Hedef İptal' } ,'OpenAPS Offline' : { cs: 'OpenAPS vypnuto' @@ -11596,12 +12769,16 @@ function init() { ,fr: 'OpenAPS déconnecté' ,ru: 'OpenAPS вне сети' ,fi: 'OpenAPS poissa verkosta' + ,ko: 'OpenAPS Offline' ,it: 'OpenAPS disconnesso' ,es: 'OpenAPS desconectado' ,nl: 'OpenAPS Offline' + ,zh_cn: 'OpenAPS 离线' ,sv: 'OpenAPS Offline' ,bg: 'OpenAPS спрян' + ,hr: 'OpenAPS odspojen' ,pl: 'OpenAPS nieaktywny' + ,tr: 'OpenAPS Offline (çevrimdışı)' } ,'Profiles' : { cs: 'Profily' @@ -11612,168 +12789,232 @@ function init() { ,ro: 'Profile' ,ru: 'профили' ,fi: 'Profiilit' + ,ko: '프로파일' ,it: 'Profili' ,es: 'Perfil' ,nl: 'Profielen' + ,zh_cn: '配置文件' ,sv: 'Profiler' ,bg: 'Профили' + ,hr: 'Profili' ,pl: 'Profile' + ,tr: 'Profiller' } ,'Time in fluctuation' : { cs: 'Doba měnící se glykémie' ,he: 'זמן בתנודות ' ,fi: 'Aika muutoksessa' ,fr: 'Temps passé en fluctuation' + ,ko: '변동시간' ,it: 'Tempo in fluttuazione' ,ro: 'Timp în fluctuație' ,es: 'Tiempo fluctuando' ,ru: 'время флуктуаций' ,nl: 'Tijd met fluctuaties' + ,zh_cn: '波动时间' ,sv: 'Tid i fluktation' ,de: 'Zeit in Fluktuation (Schwankung)' ,dk: 'Tid i fluktation' ,bg: 'Време в промяна' + ,hr: 'Vrijeme u fluktuaciji' ,pl: 'Czas fluaktacji (odchyleń)' + ,tr: 'Dalgalanmada geçen süre' } ,'Time in rapid fluctuation' : { cs: 'Doba rychle se měnící glykémie' ,he: 'זמן בתנודות מהירות ' ,fi: 'Aika nopeassa muutoksessa' ,fr: 'Temps passé en fluctuation rapide' + ,ko: '빠른변동시간' ,it: 'Tempo in rapida fluttuazione' ,ro: 'Timp în fluctuație rapidă' ,es: 'Tiempo fluctuando rápido' ,ru: 'время быстрых флуктуаций' ,nl: 'Tijd met grote fluctuaties' + ,zh_cn: '快速波动时间' ,sv: 'Tid i snabb fluktation' ,de: 'Zeit in starker Fluktuation (Schwankung)' ,dk: 'Tid i hurtig fluktation' ,bg: 'Време в бърза промяна' + ,hr: 'Vrijeme u brzoj fluktuaciji' ,pl: 'Czas szybkich fluaktacji (odchyleń)' + ,tr: 'Hızlı dalgalanmalarda geçen süre' } ,'This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from:' : { cs: 'Toto je pouze hrubý odhad, který může být nepřesný a nenahrazuje kontrolu z krve. Vzorec je převzatý z:' ,he: 'זוהי רק הערכה גסה שיכולה להיות מאוד לא מדויקת ואינה מחליפה את בדיקת הדם בפועל. הנוסחה המשמשת נלקחת מ: ' ,fi: 'Tämä on epätarkka arvio joka saattaa heittää huomattavasti mittaustuloksesta, eikä korvaa laboratoriotestiä. Laskentakaava on otettu artikkelista: ' ,fr: 'Ceci est seulement une estimation grossière qui peut être très imprécise et ne remplace pas une mesure sanguine adéquate. La formule est empruntée à l\'article:' + ,ko: '이것은 대충 예측한 것이기 때문에 부정확할 수 있고 실제 혈당으로 대체되지 않습니다. 사용된 공식:' ,it: 'Questa è solo un\'approssimazione che può essere molto inaccurata e che non sostituisce la misurazione capillare. La formula usata è presa da:' ,ro: 'Aceasta este doar o aproximare brută, care poate fi foarte imprecisă și nu ține loc de testare capilară. Formula matematică folosită este luată din:' ,es: 'Esto es sólo una estimación apróximada que puede ser muy inexacta y no reemplaza las pruebas de sangre reales. La fórmula utilizada está tomada de: ' ,ru: 'Это приблизительная оценка не заменяющая фактический анализ крови. Используемая формула взята из:' + ,zh_cn: '这只是一个粗略的估计,可能非常不准确,并不能取代测指血' ,nl: 'Dit is enkel een grove schatting die onjuist kan zijn welke geen bloedtest vervangt. De gebruikte formule is afkomstig van:' ,sv: 'Detta är en grov uppskattning som kan vara missvisande. Det ersätter inte blodprov. Formeln är hämtad från:' ,de: 'Dies ist lediglich eine grobe Schätzung, die sehr ungenau sein kann und eine Überprüfung des tatsächlichen Blutzuckers nicht ersetzen kann. Die verwendete Formel wurde genommen von:' ,dk: 'Dette er kun en grov estimering som kan være misvisende. Det erstatter ikke en blodprøve. Formelen er hemtet fra:' ,bg: 'Това е само грубо изчисление, което може да е много неточно и не изключва реалния кръвен тест. Формулата, кокято е използвана е взета от:' + ,hr: 'Ovo je samo gruba procjena koja može biti neprecizna i ne mijenja testiranje iz krvi. Formula je uzeta iz:' ,pl: 'To tylko przybliżona ocena, która może być bardzo niedokładna i nie może zastąpić faktycznego poziomu cukru we krwi. Zastosowano formułę:' + ,tr: 'Bu bir kaba tahmindir ve çok hata içerebilir gerçek kan şekeri testlerinin yerini tutmayacaktır. Kullanılan formülde buradandır:' } , 'Filter by hours' : { cs: ' Filtr podle hodin' ,fi: 'Huomioi raportissa seuraavat tunnit' + ,ko: '시간으로 정렬' ,it: 'Filtra per ore' ,fr: 'Filtrer par heures' ,ro: 'Filtrare pe ore' ,es: 'Filtrar por horas' ,ru: 'почасовой фильтр' ,nl: 'Filter op uren' + ,zh_cn: '按小时过滤' ,sv: 'Filtrera per timme' ,de: 'Filtern nach Stunden' ,dk: 'Filtrer per time' ,bg: 'Филтър по часове' + ,hr: 'Filter po satima' ,pl: 'Filtruj po godzinach' + ,tr: 'Saatlere göre filtrele' } , 'Time in fluctuation and Time in rapid fluctuation measure the % of time during the examined period, during which the blood glucose has been changing relatively fast or rapidly. Lower values are better.' : { cs: 'Doba měnící se glykémie a rapidně se měnící glykémie měří % času ve zkoumaném období, během kterého se glykémie měnila relativně rychle nebo rapidně. Nižší hodnota je lepší.' ,fi: 'Aika Muutoksessa ja Aika Nopeassa Muutoksessa mittaa osuutta tarkkailtavasta aikaperiodista, jolloin glukoosi on ollut nopeassa tai hyvin nopeassa muutoksessa. Pienempi arvo on parempi.' ,fr: 'Le Temps passé en fluctuation et le temps passé en fluctuation rapide mesurent la part de temps durant la période examinée, pendant laquelle la glycémie a évolué relativement ou très rapidement. Les valeurs basses sont les meilleures.' + ,ko: '변동시간과 빠른 변동시간은 조사된 기간 동안 %의 시간으로 측정되었습니다.혈당은 비교적 빠르게 변화되었습니다. 낮을수록 좋습니다.' ,it: 'Tempo in fluttuazione e Tempo in rapida fluttuazione misurano la % di tempo durante il periodo esaminato, durante il quale la glicemia stà variando velocemente o rapidamente. Bassi valori sono migliori.' ,ro: 'Timpul în fluctuație și timpul în fluctuație rapidă măsoară procentul de timp, din perioada examinată, în care glicemia din sânge a avut o variație relativ rapidă sau rapidă. Valorile mici sunt de preferat.' ,es: 'Tiempo en fluctuación y Tiempo en fluctuación rápida miden el % de tiempo del período exáminado, durante la cual la glucosa en sangre ha estado cambiando relativamente rápido o rápidamente. Valores más bajos son mejores.' ,ru: 'время флуктуаций и время быстрых флуктуаций означает % времени в рассматриваемый период в течение которого СК менялся относительно быстро или просто быстро. Более низкие значения предпочтительней' ,nl: 'Tijd met fluctuaties of grote fluctuaties in % van de geevalueerde periode, waarbij de bloed glucose relatief snel wijzigde.Lagere waarden zijn beter.' + ,zh_cn: '在检查期间血糖波动时间和快速波动时间占的时间百分比,在此期间血糖相对快速或快速地变化。百分比值越低越好。' ,sv: 'Tid i fluktuation och tid i snabb fluktuation mäter% av tiden under den undersökta perioden, under vilken blodsockret har förändrats relativt snabbt eller snabbt. Lägre värden är bättre' ,de: 'Zeit in Fluktuation und Zeit in starker Fluktuation messen den Teil der Zeit, in der sich der Blutzuckerwert relativ oder sehr schnell verändert hat. Niedrigere Werte sind besser.' ,dk: 'Tid i fluktuation og tid i hurtig fluktuation måler % af tiden i den undersøgte periode, under vilket blodsukkret har ændret sig relativt hurtigt. Lavere værdier er bedre.' ,bg: 'Време в промяна и време в бърза промяна измерват % от време в разгледания период, през който КЗ са се променяли бързо или много бързо. По-ниски стойности са по-добри.' + ,hr: 'Vrijeme u fluktuaciji i vrijeme u brzoj fluktuaciji mjere % vremena u gledanom periodu, tijekom kojeg se GUK mijenja relativno brzo ili brzo. Niže vrijednosti su bolje.' ,pl: 'Czas fluktuacji i szybki czas fluktuacji mierzą % czasu w badanym okresie, w którym poziom glukozy we krwi zmieniał się szybko lub bardzo szybko. Preferowane są wolniejsze zmiany' + ,tr: 'Dalgalanmadaki zaman ve Hızlı dalgalanmadaki zaman, kan şekerinin nispeten hızlı veya çok hızlı bir şekilde değiştiği, incelenen dönemdeki zamanın %\'sini ölçer. Düşük değerler daha iyidir.' } , 'Mean Total Daily Change is a sum of the absolute value of all glucose excursions for the examined period, divided by the number of days. Lower is better.' : { cs: 'Průměrná celková denní změna je součet absolutních hodnoty všech glykémií za sledované období, děleno počtem dní. Nižší hodnota je lepší.' ,fi: 'Keskimääräinen Kokonaismuutos kertoo kerkimääräisen päivätason verensokerimuutoksien yhteenlasketun arvon. Pienempi arvo on parempi.' ,fr: 'La Variation Totale Journalière Moyenne est la somme de toute les excursions glycémiques absolues pour une période analysée, divisée par le nombre de jours. Les valeurs basses sont les meilleures.' + ,ko: '전체 일일 변동 평균은 조사된 기간동안 전체 혈당 절대값의 합을 전체 일수로 나눈 값입니다. 낮을수록 좋습니다.' ,it: 'Media Totale Giornaliera Variazioni è la somma dei valori assoluti di tutte le escursioni glicemiche per il periodo esaminato, diviso per il numero di giorni. Bassi valori sono migliori.' ,ro: 'Schimbarea medie totală zilnică este suma valorilor absolute ale tuturor excursiilor glicemice din perioada examinată, împărțite la numărul de zile. Valorile mici sunt de preferat.' ,ru: 'усредненное ежедневное изменение это сумма абсолютных величин всех отклонений СК в рассматриваемый период, деленное на количество дней. Меньшая величина предпочтительней' ,es: 'El cambio medio diario total es la suma de los valores absolutos de todas las glucémias en el período examinado, dividido por el número de días. Mejor valores bajos.' ,nl: 'Gemiddelde veranderingen per dag is een som van alle waardes die uitschieten over de bekeken periode, gedeeld door het aantal dagen in deze periode. Lager is beter.' + ,zh_cn: '平均每日总变化是检查期间所有血糖偏移的绝对值之和除以天数。越低越好' ,sv: 'Medel Total Daglig Förändring är summan av absolutvärdet av alla glukosförändringar under den undersökta perioden, dividerat med antalet dagar. Lägre är bättre.' ,de: 'Die gesamte mittlere Änderung pro Tag ist die Summe der absoluten Werte aller Glukoseveränderungen im Betrachtungszeitraum geteilt durch die Anzahl der Tage. Niedrigere Werte sind besser.' ,dk: 'Middel Total Daglig Ændring er summen af absolutværdier af alla glukoseændringer i den undersøgte periode, divideret med antallet af dage. Lavere er bedre.' ,bg: 'Средната дневна промяна е сумата на всички промени в стойностите на КЗ за разгледания период, разделена на броя дни в периода. По-ниската стойност е по-добра' + ,hr: 'Srednja ukupna dnevna promjena je suma apsolutnih vrijednosti svih pomaka u gledanom periodu, podijeljeno s brojem dana. Niže vrijednosti su bolje.' ,pl: 'Sednia całkowita dziennych zmian jest sumą wszystkich zmian glikemii w badanym okresie, podzielonym przez liczbę dni. Mniejsze są lepsze' + ,tr: 'Toplam Günlük Değişim, incelenen süre için, gün sayısına bölünen tüm glukoz değerlerinin mutlak değerinin toplamıdır. Düşük değer daha iyidir.' } , 'Mean Hourly Change is a sum of the absolute value of all glucose excursions for the examined period, divided by the number of hours in the period. Lower is better.' : { cs: 'Průměrná hodinová změna je součet absolutní hodnoty všech glykémií za sledované období, dělených počtem hodin v daném období. Nižší hodnota je lepší.' ,fi: 'Keskimääräinen tunti kertoo keskimääräisen tuntitason verensokerimuutoksien yhteenlasketun arvon. Pienempi arvo on parempi.' ,fr: 'La Variation Horaire Moyenne est la somme de toute les excursions glycémiques absolues pour une période analysée, divisée par le nombre d\'heures dans la période. Les valeures basses sont les meilleures.' + ,ko: '시간당 변동 평균은 조사된 기간 동안 전체 혈당 절대값의 합을 기간의 시간으로 나눈 값입니다.낮을수록 좋습니다.' ,it: 'Media Oraria Variazioni è la somma del valore assoluto di tutte le escursioni glicemiche per il periodo esaminato, diviso per il numero di ore. Bassi valori sono migliori.' ,ro: 'Variația media orară este suma valorilor absolute ale tuturor excursiilor glicemice din perioada examinată, împărțite la numărul de ore din aceeași perioadă. Valorile mici sunt de preferat.' ,ru: 'усредненное часовое изменение это сумма абсолютных величин всех отклонений СК в рассматриваемый период, деленное на количество часов в этот период. Более низкое предпочтительней' ,es: 'El cambio medio por hora, es la suma del valor absoluto de todas las glucemias para el período examinado, dividido por el número de horas en el período. Más bajo es mejor.' ,nl: 'Gemiddelde veranderingen per uur is een som van alle waardes die uitschieten over de bekeken periode, gedeeld door het aantal uur in deze periode. Lager is beter.' + ,zh_cn: '平均每小时变化是检查期间所有血糖偏移的绝对值之和除以该期间的小时数。 越低越好' ,sv: 'Medelvärde per timme är summan av absolutvärdet av alla glukosförändringar under den undersökta perioden dividerat med antalet timmar under perioden. Lägre är bättre.' ,de: 'Die mittlere Änderung pro Stunde ist die Summe der absoluten Werte aller Glukoseveränderungen im Betrachtungszeitraum geteilt durch die Anzahl der Stunden. Niedrigere Werte sind besser.' ,dk: 'Middelværdier per time er summen af absolutværdier fra alle glukoseændringer i den undersøgte periode divideret med antallet af timer. Lavere er bedre.' ,bg: 'Средната промяна за час е сумата на всички промени в стойностите на КЗ за разгледания период, разделена на броя часове в периода. По-ниската стойност е по-добра' + ,hr: 'Srednja ukupna promjena po satu je suma apsolutnih vrijednosti svih pomaka u gledanom periodu, podijeljeno s brojem sati. Niže vrijednosti su bolje.' ,pl: 'Sednia całkowita godzinnych zmian jest sumą wszystkich zmian glikemii w badanym okresie, podzielonym przez liczbę godzin. Mniejsze są lepsze' - } - , 'GVI and PGS are measures developed by Dexcom, detailed here.' : { - cs: 'GVI a PGS jsou měření vyvinutá společností Dexcom, podrobněji zde.' - ,he: 'GVI and PGS are measures developed by Dexcom, detailed here. ' - ,fi: 'GVI ja PGS ovat Dexcom-yrityksen kehittämiä mittaustapoja, joista voit lukea lisää täällä..' - ,it: 'GVI e PGS sono misure sviluppate da Dexcom, dettagliate qui.' - ,es: 'Variabilidad de la glucosa en sangre y el estado glucémico del paciente es un valor diseñado por Dexcom, más detalles en here.' - ,fr: 'GVI et PGS sont des mesures développées par la firme Dexcom, présentées en détail ici.' - ,ro: 'GVI și PGS sunt caracteristici de măsurare inventate de Dexcom, ale căror detalii le găsiți aici.' - ,ru: 'вариабельность гликемии и статус гликемии больного это величины, разработанные декскомом, подробнее here.' - ,nl: 'GVI en PGS zijn maten ontworpen door Dexcom, gedetaillieerde info here.' - ,sv: 'GVI och PGS är värden utvecklade av Dexcom, detaljer här.' - ,de: 'GVI und PGS sind von Dexcom entwickelte Werte. Details hier.' - ,dk: 'GVI og PGS er værdier udviklet af Dexcom, detaljer her.' - ,bg: 'GVI и PGS са параметри разработени от Декском, повече детайли тук.' - ,pl: 'GVI i PGS są pomiarami opracowanymi przez Dexcom, szczegóły tutaj.' + ,tr: 'Saat başına ortalama değişim, gözlem periyodu üzerindeki tüm glikoz değişikliklerinin mutlak değerlerinin saat sayısına bölünmesiyle elde edilen toplam değerdir. Düşük değerler daha iyidir.' + } + , 'GVI (Glycemic Variability Index) and PGS (Patient Glycemic Status) are measures developed by Dexcom, detailed can be found here.' : { + cs: '">zde.' + ,he: '">here.' + ,fi: '">here.' + ,ko: '">here.' + ,it: '">qui.' + ,es: '">here.' + ,fr: '">ici.' + ,ro: '">aici.' + ,ru: '">здесь.' + ,nl: '">is hier te vinden.' + ,zh_cn: '">here.' + ,sv: '">här.' + ,de: '">hier.' + ,dk: '">her.' + ,bg: '">тук.' + ,hr: '">ovdje.' + ,pl: '">tutaj.' + ,tr: '">buradan.' } , 'Mean Total Daily Change' : { cs: 'Průměrná celková denní změna' ,he: 'שינוי יומי ממוצע ' ,fi: 'Keskimääräinen Kokonaismuutos' ,fr: 'Variation Totale Journalière Moyenne' + ,ko: '전체 일일 변동 평균' ,it: 'Media Totale Giornaliera Variazioni' ,ro: 'Variația medie totală zilnică' ,ru: 'усредненное изменение за день' ,es: 'Variación media total diaria' ,nl: 'Gemiddelde veranderingen per dag' + ,zh_cn: '平均每日总变化' ,sv: 'Medel Total Daglig Förändring' ,de: 'Gesamte mittlere Änderung pro Tag' ,dk: 'Middel Total Daglig Ændring' ,bg: 'Средна промяна за ден' + ,hr: 'Srednja ukupna dnevna promjena' ,pl: 'Średnia całkowita dziennych zmian' + ,tr: 'Günde toplam ortalama değişim' } , 'Mean Hourly Change' : { cs: 'Průměrná hodinová změna' ,he: 'שינוי ממוצע לשעה ' ,fi: 'Keskimääräinen tuntimuutos' ,fr: 'Variation Horaire Moyenne' + ,ko: '시간당 변동 평균' ,it: 'Media Oraria Variazioni' ,ro: 'Variația medie orară' ,es: 'Variación media total por horas' ,ru: 'усредненное изменение за час' ,nl: 'Gemiddelde veranderingen per uur' + ,zh_cn: '平均每小时变化' ,sv: 'Medelvärde per timme' ,de: 'Mittlere Änderung pro Stunde' ,dk: 'Middelværdier per time' ,bg: 'Средна промяна за час' + ,hr: 'Srednja ukupna promjena po satu' ,pl: 'Średnia całkowita godzinnych zmian' + ,tr: 'Saatte ortalama değişim' } , 'FortyFiveDown': { bg: 'slightly dropping' @@ -11786,10 +13027,10 @@ function init() { , fi: 'laskee hitaasti' , fr: 'en chute lente' , he: 'slightly dropping' - , hr: 'slightly dropping' - , it: 'leggera diminuzione' + , hr: 'sporo padajuće' , ko: 'slightly dropping' - , nb: 'slightly dropping' + , it: 'leggera diminuzione' + , nb: 'svakt fallende' , pl: 'niewielki spadek' , pt: 'slightly dropping' , ro: 'scădere ușoară' @@ -11797,14 +13038,15 @@ function init() { , sk: 'slightly dropping' , sv: 'slightly dropping' , nl: 'slightly dropping' - , zh_cn: 'slightly dropping' + , tr: 'biraz düşen' + , zh_cn: '缓慢下降' , zh_tw: 'slightly dropping' - + }, 'FortyFiveUp': { bg: 'slightly rising' , cs: 'lehce nahoru' - , de: 'leicht fallend' + , de: 'leicht steigend' , dk: 'svagt stigende' , el: 'slightly rising' , en: 'slightly rising' @@ -11812,10 +13054,10 @@ function init() { , fi: 'nousee hitaasti' , fr: 'en montée lente' , he: 'slightly rising' - , hr: 'slightly rising' + , hr: 'sporo rastuće' , it: 'leggero aumento' , ko: 'slightly rising' - , nb: 'slightly rising' + , nb: 'svakt stigende' , pl: 'niewielki wzrost' , pt: 'slightly rising' , ro: 'creștere ușoară' @@ -11823,7 +13065,8 @@ function init() { , sk: 'slightly rising' , sv: 'slightly rising' , nl: 'slightly rising' - , zh_cn: 'slightly rising' + , tr: 'biraz yükselen' + , zh_cn: '缓慢上升' , zh_tw: 'slightly rising' }, 'Flat': { @@ -11837,10 +13080,10 @@ function init() { , fi: 'tasainen' , fr: 'stable' , he: 'holding' - , hr: 'holding' + , hr: 'ravno' , it: 'stabile' , ko: 'holding' - , nb: 'holding' + , nb: 'stabilt' , pl: 'stabilny' , pt: 'holding' , ro: 'stabil' @@ -11848,7 +13091,8 @@ function init() { , sk: 'holding' , sv: 'holding' , nl: 'holding' - , zh_cn: 'holding' + , tr: 'sabit' + , zh_cn: '平' , zh_tw: 'holding' }, 'SingleUp': { @@ -11862,10 +13106,10 @@ function init() { , fi: 'nousussa' , fr: 'en montée' , he: 'rising' - , hr: 'rising' + , hr: 'rastuće' , it: 'aumento' , ko: 'rising' - , nb: 'rising' + , nb: 'stigende' , pl: 'wzrost' , pt: 'rising' , ro: 'creștere' @@ -11873,7 +13117,8 @@ function init() { , sk: 'rising' , sv: 'rising' , nl: 'rising' - , zh_cn: 'rising' + , tr: 'yükseliyor' + , zh_cn: '上升' , zh_tw: 'rising' }, 'SingleDown': { @@ -11887,10 +13132,10 @@ function init() { , fi: 'laskussa' , fr: 'en chute' , he: 'dropping' - , hr: 'dropping' + , hr: 'padajuće' , it: 'diminuzione' , ko: 'dropping' - , nb: 'dropping' + , nb: 'fallende' , pl: 'spada' , pt: 'dropping' , ro: 'scădere' @@ -11898,7 +13143,8 @@ function init() { , sk: 'dropping' , sv: 'dropping' , nl: 'dropping' - , zh_cn: 'dropping' + , tr: 'düşüyor' + , zh_cn: '下降' , zh_tw: 'dropping' }, 'DoubleDown': { @@ -11912,10 +13158,10 @@ function init() { , fi: 'laskee nopeasti' , fr: 'en chute rapide' , he: 'rapidly dropping' - , hr: 'rapidly dropping' + , hr: 'brzo padajuće' , it: 'rapida diminuzione' , ko: 'rapidly dropping' - , nb: 'rapidly dropping' + , nb: 'hurtig stigende' , pl: 'szybko spada' , pt: 'rapidly dropping' , ro: 'scădere bruscă' @@ -11923,7 +13169,8 @@ function init() { , sk: 'rapidly dropping' , sv: 'rapidly dropping' , nl: 'rapidly dropping' - , zh_cn: 'rapidly dropping' + , tr: 'hızlı düşen' + , zh_cn: '快速下降' , zh_tw: 'rapidly dropping' }, 'DoubleUp': { @@ -11937,21 +13184,386 @@ function init() { , fi: 'nousee nopeasti' , fr: 'en montée rapide' , he: 'rapidly rising' - , hr: 'rapidly rising' + , hr: 'brzo rastuće' , it: 'rapido aumento' , ko: 'rapidly rising' - , nb: 'rapidly rising' + , nb: 'hurtig fallende' , pl: 'szybko rośnie' , pt: 'rapidly rising' , ro: 'creștere rapidă' - , ru: 'быстро растет' + , ru: 'быстрый рост' , sk: 'rapidly rising' , sv: 'rapidly rising' , nl: 'rapidly rising' - , zh_cn: 'rapidly rising' + , tr: 'hızla yükselen' + , zh_cn: '快速上升' , zh_tw: 'rapidly rising' }, - 'alexaStatus': { + 'virtAsstUnknown': { + bg: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , cs: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , de: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , dk: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , el: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , en: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , es: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , fi: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , fr: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , he: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , hr: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , it: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , ko: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , nb: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , pl: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , pt: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , ro: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , nl: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , ru: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , sk: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , sv: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , tr: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , zh_cn: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + , zh_tw: 'That value is unknown at the moment. Please see your Nightscout site for more details.' + }, + 'virtAsstTitleAR2Forecast': { + bg: 'AR2 Forecast' + , cs: 'AR2 Forecast' + , de: 'AR2 Forecast' + , dk: 'AR2 Forecast' + , el: 'AR2 Forecast' + , en: 'AR2 Forecast' + , es: 'AR2 Forecast' + , fi: 'AR2 Forecast' + , fr: 'AR2 Forecast' + , he: 'AR2 Forecast' + , hr: 'AR2 Forecast' + , it: 'AR2 Forecast' + , ko: 'AR2 Forecast' + , nb: 'AR2 Forecast' + , pl: 'AR2 Forecast' + , pt: 'AR2 Forecast' + , ro: 'AR2 Forecast' + , nl: 'AR2 Forecast' + , ru: 'AR2 Forecast' + , sk: 'AR2 Forecast' + , sv: 'AR2 Forecast' + , tr: 'AR2 Forecast' + , zh_cn: 'AR2 Forecast' + , zh_tw: 'AR2 Forecast' + }, + 'virtAsstTitleCurrentBasal': { + bg: 'Current Basal' + , cs: 'Current Basal' + , de: 'Current Basal' + , dk: 'Current Basal' + , el: 'Current Basal' + , en: 'Current Basal' + , es: 'Current Basal' + , fi: 'Current Basal' + , fr: 'Current Basal' + , he: 'Current Basal' + , hr: 'Current Basal' + , it: 'Current Basal' + , ko: 'Current Basal' + , nb: 'Current Basal' + , pl: 'Current Basal' + , pt: 'Current Basal' + , ro: 'Current Basal' + , nl: 'Current Basal' + , ru: 'Current Basal' + , sk: 'Current Basal' + , sv: 'Current Basal' + , tr: 'Current Basal' + , zh_cn: 'Current Basal' + , zh_tw: 'Current Basal' + }, + 'virtAsstTitleCurrentCOB': { + bg: 'Current COB' + , cs: 'Current COB' + , de: 'Current COB' + , dk: 'Current COB' + , el: 'Current COB' + , en: 'Current COB' + , es: 'Current COB' + , fi: 'Current COB' + , fr: 'Current COB' + , he: 'Current COB' + , hr: 'Current COB' + , it: 'Current COB' + , ko: 'Current COB' + , nb: 'Current COB' + , pl: 'Current COB' + , pt: 'Current COB' + , ro: 'Current COB' + , nl: 'Current COB' + , ru: 'Current COB' + , sk: 'Current COB' + , sv: 'Current COB' + , tr: 'Current COB' + , zh_cn: 'Current COB' + , zh_tw: 'Current COB' + }, + 'virtAsstTitleCurrentIOB': { + bg: 'Current IOB' + , cs: 'Current IOB' + , de: 'Current IOB' + , dk: 'Current IOB' + , el: 'Current IOB' + , en: 'Current IOB' + , es: 'Current IOB' + , fi: 'Current IOB' + , fr: 'Current IOB' + , he: 'Current IOB' + , hr: 'Current IOB' + , it: 'Current IOB' + , ko: 'Current IOB' + , nb: 'Current IOB' + , pl: 'Current IOB' + , pt: 'Current IOB' + , ro: 'Current IOB' + , nl: 'Current IOB' + , ru: 'Current IOB' + , sk: 'Current IOB' + , sv: 'Current IOB' + , tr: 'Current IOB' + , zh_cn: 'Current IOB' + , zh_tw: 'Current IOB' + }, + 'virtAsstTitleLoopForecast': { + bg: 'Loop Forecast' + , cs: 'Loop Forecast' + , de: 'Loop Forecast' + , dk: 'Loop Forecast' + , el: 'Loop Forecast' + , en: 'Loop Forecast' + , es: 'Loop Forecast' + , fi: 'Loop Forecast' + , fr: 'Loop Forecast' + , he: 'Loop Forecast' + , hr: 'Loop Forecast' + , it: 'Loop Forecast' + , ko: 'Loop Forecast' + , nb: 'Loop Forecast' + , pl: 'Loop Forecast' + , pt: 'Loop Forecast' + , ro: 'Loop Forecast' + , nl: 'Loop Forecast' + , ru: 'Loop Forecast' + , sk: 'Loop Forecast' + , sv: 'Loop Forecast' + , tr: 'Loop Forecast' + , zh_cn: 'Loop Forecast' + , zh_tw: 'Loop Forecast' + }, + 'virtAsstTitleLastLoop': { + bg: 'Last Loop' + , cs: 'Last Loop' + , de: 'Last Loop' + , dk: 'Last Loop' + , el: 'Last Loop' + , en: 'Last Loop' + , es: 'Last Loop' + , fi: 'Last Loop' + , fr: 'Last Loop' + , he: 'Last Loop' + , hr: 'Last Loop' + , it: 'Last Loop' + , ko: 'Last Loop' + , nb: 'Last Loop' + , pl: 'Last Loop' + , pt: 'Last Loop' + , ro: 'Last Loop' + , nl: 'Last Loop' + , ru: 'Last Loop' + , sk: 'Last Loop' + , sv: 'Last Loop' + , tr: 'Last Loop' + , zh_cn: 'Last Loop' + , zh_tw: 'Last Loop' + }, + 'virtAsstTitleOpenAPSForecast': { + bg: 'OpenAPS Forecast' + , cs: 'OpenAPS Forecast' + , de: 'OpenAPS Forecast' + , dk: 'OpenAPS Forecast' + , el: 'OpenAPS Forecast' + , en: 'OpenAPS Forecast' + , es: 'OpenAPS Forecast' + , fi: 'OpenAPS Forecast' + , fr: 'OpenAPS Forecast' + , he: 'OpenAPS Forecast' + , hr: 'OpenAPS Forecast' + , it: 'OpenAPS Forecast' + , ko: 'OpenAPS Forecast' + , nb: 'OpenAPS Forecast' + , pl: 'OpenAPS Forecast' + , pt: 'OpenAPS Forecast' + , ro: 'OpenAPS Forecast' + , nl: 'OpenAPS Forecast' + , ru: 'OpenAPS Forecast' + , sk: 'OpenAPS Forecast' + , sv: 'OpenAPS Forecast' + , tr: 'OpenAPS Forecast' + , zh_cn: 'OpenAPS Forecast' + , zh_tw: 'OpenAPS Forecast' + }, + 'virtAsstTitlePumpReservoir': { + bg: 'Insulin Remaining' + , cs: 'Insulin Remaining' + , de: 'Insulin Remaining' + , dk: 'Insulin Remaining' + , el: 'Insulin Remaining' + , en: 'Insulin Remaining' + , es: 'Insulin Remaining' + , fi: 'Insulin Remaining' + , fr: 'Insulin Remaining' + , he: 'Insulin Remaining' + , hr: 'Insulin Remaining' + , it: 'Insulin Remaining' + , ko: 'Insulin Remaining' + , nb: 'Insulin Remaining' + , pl: 'Insulin Remaining' + , pt: 'Insulin Remaining' + , ro: 'Insulin Remaining' + , nl: 'Insulin Remaining' + , ru: 'Insulin Remaining' + , sk: 'Insulin Remaining' + , sv: 'Insulin Remaining' + , tr: 'Insulin Remaining' + , zh_cn: 'Insulin Remaining' + , zh_tw: 'Insulin Remaining' + }, + 'virtAsstTitlePumpBattery': { + bg: 'Pump Battery' + , cs: 'Pump Battery' + , de: 'Pump Battery' + , dk: 'Pump Battery' + , el: 'Pump Battery' + , en: 'Pump Battery' + , es: 'Pump Battery' + , fi: 'Pump Battery' + , fr: 'Pump Battery' + , he: 'Pump Battery' + , hr: 'Pump Battery' + , it: 'Pump Battery' + , ko: 'Pump Battery' + , nb: 'Pump Battery' + , pl: 'Pump Battery' + , pt: 'Pump Battery' + , ro: 'Pump Battery' + , nl: 'Pump Battery' + , ru: 'Pump Battery' + , sk: 'Pump Battery' + , sv: 'Pump Battery' + , tr: 'Pump Battery' + , zh_cn: 'Pump Battery' + , zh_tw: 'Pump Battery' + }, + 'virtAsstTitleRawBG': { + bg: 'Current Raw BG' + , cs: 'Current Raw BG' + , de: 'Current Raw BG' + , dk: 'Current Raw BG' + , el: 'Current Raw BG' + , en: 'Current Raw BG' + , es: 'Current Raw BG' + , fi: 'Current Raw BG' + , fr: 'Current Raw BG' + , he: 'Current Raw BG' + , hr: 'Current Raw BG' + , it: 'Current Raw BG' + , ko: 'Current Raw BG' + , nb: 'Current Raw BG' + , pl: 'Current Raw BG' + , pt: 'Current Raw BG' + , ro: 'Current Raw BG' + , nl: 'Current Raw BG' + , ru: 'Current Raw BG' + , sk: 'Current Raw BG' + , sv: 'Current Raw BG' + , tr: 'Current Raw BG' + , zh_cn: 'Current Raw BG' + , zh_tw: 'Current Raw BG' + }, + 'virtAsstTitleUploaderBattery': { + bg: 'Uploader Battery' + , cs: 'Uploader Battery' + , de: 'Uploader Battery' + , dk: 'Uploader Battery' + , el: 'Uploader Battery' + , en: 'Uploader Battery' + , es: 'Uploader Battery' + , fi: 'Uploader Battery' + , fr: 'Uploader Battery' + , he: 'Uploader Battery' + , hr: 'Uploader Battery' + , it: 'Uploader Battery' + , ko: 'Uploader Battery' + , nb: 'Uploader Battery' + , pl: 'Uploader Battery' + , pt: 'Uploader Battery' + , ro: 'Uploader Battery' + , nl: 'Uploader Battery' + , ru: 'Uploader Battery' + , sk: 'Uploader Battery' + , sv: 'Uploader Battery' + , tr: 'Uploader Battery' + , zh_cn: 'Uploader Battery' + , zh_tw: 'Uploader Battery' + }, + 'virtAsstTitleCurrentBG': { + bg: 'Current BG' + , cs: 'Current BG' + , de: 'Current BG' + , dk: 'Current BG' + , el: 'Current BG' + , en: 'Current BG' + , es: 'Current BG' + , fi: 'Current BG' + , fr: 'Current BG' + , he: 'Current BG' + , hr: 'Current BG' + , it: 'Current BG' + , ko: 'Current BG' + , nb: 'Current BG' + , pl: 'Current BG' + , pt: 'Current BG' + , ro: 'Current BG' + , nl: 'Current BG' + , ru: 'Current BG' + , sk: 'Current BG' + , sv: 'Current BG' + , tr: 'Current BG' + , zh_cn: 'Current BG' + , zh_tw: 'Current BG' + }, + 'virtAsstTitleFullStatus': { + bg: 'Full Status' + , cs: 'Full Status' + , de: 'Full Status' + , dk: 'Full Status' + , el: 'Full Status' + , en: 'Full Status' + , es: 'Full Status' + , fi: 'Full Status' + , fr: 'Full Status' + , he: 'Full Status' + , hr: 'Full Status' + , it: 'Full Status' + , ko: 'Full Status' + , nb: 'Full Status' + , pl: 'Full Status' + , pt: 'Full Status' + , ro: 'Full Status' + , nl: 'Full Status' + , ru: 'Full Status' + , sk: 'Full Status' + , sv: 'Full Status' + , tr: 'Full Status' + , zh_cn: 'Full Status' + , zh_tw: 'Full Status' + }, + 'virtAsstStatus': { bg: '%1 and %2 as of %3.' , cs: '%1 %2 čas %3.' , de: '%1 und bis %3 %2.' @@ -11973,10 +13585,11 @@ function init() { , ru: '%1 и %2 начиная с %3.' , sk: '%1 and %2 as of %3.' , sv: '%1 and %2 as of %3.' - , zh_cn: '%1 and %2 as of %3.' + , tr: '%1 ve %2 e kadar %3.' + , zh_cn: '%1 和 %2 到 %3.' , zh_tw: '%1 and %2 as of %3.' }, - 'alexaBasal': { + 'virtAsstBasal': { bg: '%1 současný bazál je %2 jednotek za hodinu' , cs: '%1 current basal is %2 units per hour' , de: '%1 aktuelle Basalrate ist %2 Einheiten je Stunde' @@ -11998,10 +13611,11 @@ function init() { , sk: '%1 current basal is %2 units per hour' , sv: '%1 current basal is %2 units per hour' , nl: '%1 current basal is %2 units per hour' - , zh_cn: '%1 current basal is %2 units per hour' + , tr: '%1 geçerli bazal oranı saatte %2 ünite' + , zh_cn: '%1 当前基础率是 %2 U/小时' , zh_tw: '%1 current basal is %2 units per hour' }, - 'alexaBasalTemp': { + 'virtAsstBasalTemp': { bg: '%1 dočasný bazál %2 jednotek za hodinu skončí %3' , cs: '%1 temp basal of %2 units per hour will end %3' , de: '%1 temporäre Basalrate von %2 Einheiten endet %3' @@ -12023,10 +13637,11 @@ function init() { , sk: '%1 temp basal of %2 units per hour will end %3' , sv: '%1 temp basal of %2 units per hour will end %3' , nl: '%1 temp basal of %2 units per hour will end %3' - , zh_cn: '%1 temp basal of %2 units per hour will end %3' + , tr: '%1 geçici bazal %2 ünite %3 sona eriyor' + , zh_cn: '%1 临时基础率 %2 U/小时将会在 %3结束' , zh_tw: '%1 temp basal of %2 units per hour will end %3' }, - 'alexaIob': { + 'virtAsstIob': { bg: 'a máte %1 jednotek aktivního inzulínu.' , cs: 'and you have %1 insulin on board.' , de: 'und du hast %1 Insulin wirkend.' @@ -12048,35 +13663,37 @@ function init() { , sk: 'and you have %1 insulin on board.' , sv: 'and you have %1 insulin on board.' , nl: 'and you have %1 insulin on board.' - , zh_cn: 'and you have %1 insulin on board.' + , tr: 've Sizde %1 aktif insulin var' + , zh_cn: '并且你有 %1 的活性胰岛素.' , zh_tw: 'and you have %1 insulin on board.' }, - 'alexaIobIntent': { - bg: 'Máte %1 jednotek aktivního inzulínu' - , cs: 'You have %1 insulin on board' - , de: 'Du hast noch %1 Insulin wirkend' - , dk: 'Du har %1 insulin i kroppen' - , el: 'You have %1 insulin on board' - , en: 'You have %1 insulin on board' - , es: 'Tienes %1 insulina activa' - , fi: 'Sinulla on %1 aktiivista insuliinia' - , fr: 'You have %1 insulin on board' - , he: 'You have %1 insulin on board' - , hr: 'You have %1 insulin on board' - , it: 'Tu hai %1 insulina attiva' - , ko: 'You have %1 insulin on board' - , nb: 'You have %1 insulin on board' - , pl: 'Masz %1 aktywnej insuliny' - , pt: 'You have %1 insulin on board' - , ro: 'Aveți %1 insulină activă' - , ru: 'вы имеете %1 инсулина в организме' - , sk: 'You have %1 insulin on board' - , sv: 'You have %1 insulin on board' - , nl: 'You have %1 insulin on board' - , zh_cn: 'You have %1 insulin on board' - , zh_tw: 'You have %1 insulin on board' - }, - 'alexaIobUnits': { + 'virtAsstIobIntent': { + bg: 'Máte %1 jednotek aktivního inzulínu' + , cs: 'You have %1 insulin on board' + , de: 'Du hast noch %1 Insulin wirkend' + , dk: 'Du har %1 insulin i kroppen' + , el: 'You have %1 insulin on board' + , en: 'You have %1 insulin on board' + , es: 'Tienes %1 insulina activa' + , fi: 'Sinulla on %1 aktiivista insuliinia' + , fr: 'You have %1 insulin on board' + , he: 'You have %1 insulin on board' + , hr: 'You have %1 insulin on board' + , it: 'Tu hai %1 insulina attiva' + , ko: 'You have %1 insulin on board' + , nb: 'You have %1 insulin on board' + , pl: 'Masz %1 aktywnej insuliny' + , pt: 'You have %1 insulin on board' + , ro: 'Aveți %1 insulină activă' + , ru: 'вы имеете %1 инсулина в организме' + , sk: 'You have %1 insulin on board' + , sv: 'You have %1 insulin on board' + , nl: 'You have %1 insulin on board' + , tr: 'Sizde %1 aktif insülin var' + , zh_cn: '你有 %1 的活性胰岛素' + , zh_tw: 'You have %1 insulin on board' + }, + 'virtAsstIobUnits': { bg: '%1 units of' , cs: '%1 jednotek' , de: 'noch %1 Einheiten' @@ -12098,10 +13715,11 @@ function init() { , ru: '%1 единиц' , sk: '%1 units of' , sv: '%1 units of' - , zh_cn: '%1 units of' + , tr: 'hala %1 birim' + , zh_cn: '%1 单位' , zh_tw: '%1 units of' }, - 'alexaPreamble': { + 'virtAsstPreamble': { bg: 'Your' , cs: 'Vaše' , de: 'Deine' @@ -12123,10 +13741,11 @@ function init() { , ru: 'ваш' , sk: 'Your' , sv: 'Your' - , zh_cn: 'Your' + , tr: 'Senin' + , zh_cn: '你的' , zh_tw: 'Your' }, - 'alexaPreamble3person': { + 'virtAsstPreamble3person': { bg: '%1 has a ' , cs: '%1 má ' , de: '%1 hat eine' @@ -12148,10 +13767,11 @@ function init() { , ru: '%1 имеет ' , sk: '%1 has a ' , sv: '%1 has a ' - , zh_cn: '%1 has a ' + , tr: '%1 bir tane var' + , zh_cn: '%1 有一个 ' , zh_tw: '%1 has a ' }, - 'alexaNoInsulin': { + 'virtAsstNoInsulin': { bg: 'no' , cs: 'žádný' , de: 'kein' @@ -12173,128 +13793,316 @@ function init() { , ru: 'нет' , sk: 'no' , sv: 'no' - , zh_cn: 'no' + , tr: 'yok' + , zh_cn: '否' , zh_tw: 'no' }, - 'alexaUploadBattery': { - bg: 'Your uploader battery is at %1' - ,cs: 'Baterie mobilu má %1' - , en: 'Your uploader battery is at %1' - , de: 'Der Akku deines Uploader Handys ist bei %1' - , dk: 'Din uploaders batteri er %1' - , nl: 'De batterij van je mobiel is bij %l' - , sv: 'Din uppladdares batteri är %1' - , fi: 'Lähettimen paristoa jäljellä %1' - , ro: 'Bateria uploaderului este la %1' - , pl: 'Twoja bateria ma %1' - }, - 'alexaReservoir': { - bg: 'You have %1 units remaining' - , cs: 'V zásobníku zbývá %1 jednotek' - , en: 'You have %1 units remaining' - , de: 'Du hast %1 Einheiten übrig' - , dk: 'Du har %1 enheder tilbage' - , nl: 'Je hebt nog %l eenheden in je reservoir' - , sv: 'Du har %1 enheter kvar' - , fi: '%1 yksikköä insuliinia jäljellä' - , ro: 'Mai aveți %1 unități rămase' - , pl: 'W zbiorniku pozostało %1 jednostek' - }, - 'alexaPumpBattery': { - bg: 'Your pump battery is at %1 %2' - , cs: 'Baterie v pumpě má %1 %2' - , en: 'Your pump battery is at %1 %2' - , de: 'Der Batteriestand deiner Pumpe ist bei %1 %2' - , dk: 'Din pumpes batteri er %1 %2' - , nl: 'Je pomp batterij is bij %1 %2' - , sv: 'Din pumps batteri är %1 %2' - , fi: 'Pumppu on %1 %2' - , ro: 'Bateria pompei este la %1 %2' - , pl: 'Bateria pompy jest w %1 %2' - }, - 'alexaLastLoop': { - bg: 'The last successful loop was %1' - , cs: 'Poslední úšpěšné provedení smyčky %1' - , en: 'The last successful loop was %1' - , de: 'Der letzte erfolgreiche Loop war %1' - , dk: 'Seneste successfulde loop var %1' - , nl: 'De meest recente goede loop was %1' - , sv: 'Senaste lyckade loop var %1' - , fi: 'Viimeisin onnistunut loop oli %1' - , ro: 'Ultima decizie loop implementată cu succes a fost %1' - , pl: 'Ostatnia pomyślna pętla była %1' - }, - 'alexaLoopNotAvailable': { + 'virtAsstUploadBattery': { + bg: 'Your uploader battery is at %1' + , cs: 'Baterie mobilu má %1' + , en: 'Your uploader battery is at %1' + , hr: 'Your uploader battery is at %1' + , de: 'Der Akku deines Uploader Handys ist bei %1' + , dk: 'Din uploaders batteri er %1' + , ko: 'Your uploader battery is at %1' + , nl: 'De batterij van je mobiel is bij %l' + , zh_cn: '你的手机电池电量是 %1 ' + , sv: 'Din uppladdares batteri är %1' + , fi: 'Lähettimen paristoa jäljellä %1' + , ro: 'Bateria uploaderului este la %1' + , pl: 'Twoja bateria ma %1' + , ru: 'батарея загрузчика %1' + , tr: 'Yükleyici piliniz %1' + }, + 'virtAsstReservoir': { + bg: 'You have %1 units remaining' + , cs: 'V zásobníku zbývá %1 jednotek' + , en: 'You have %1 units remaining' + , hr: 'You have %1 units remaining' + , de: 'Du hast %1 Einheiten übrig' + , dk: 'Du har %1 enheder tilbage' + , ko: 'You have %1 units remaining' + , nl: 'Je hebt nog %l eenheden in je reservoir' + , zh_cn: '你剩余%1 U的胰岛素' + , sv: 'Du har %1 enheter kvar' + , fi: '%1 yksikköä insuliinia jäljellä' + , ro: 'Mai aveți %1 unități rămase' + , pl: 'W zbiorniku pozostało %1 jednostek' + , ru: 'остается %1 ед' + , tr: '%1 birim kaldı' + }, + 'virtAsstPumpBattery': { + bg: 'Your pump battery is at %1 %2' + , cs: 'Baterie v pumpě má %1 %2' + , en: 'Your pump battery is at %1 %2' + , hr: 'Your pump battery is at %1 %2' + , de: 'Der Batteriestand deiner Pumpe ist bei %1 %2' + , dk: 'Din pumpes batteri er %1 %2' + , ko: 'Your pump battery is at %1 %2' + , nl: 'Je pomp batterij is bij %1 %2' + , zh_cn: '你的泵电池电量是%1 %2' + , sv: 'Din pumps batteri är %1 %2' + , fi: 'Pumppu on %1 %2' + , ro: 'Bateria pompei este la %1 %2' + , pl: 'Bateria pompy jest w %1 %2' + , ru: 'батарея помпы %1 %2' + , tr: 'Pompa piliniz %1 %2' + }, + 'virtAsstUploaderBattery': { + bg: 'Your uploader battery is at %1' + , cs: 'Your uploader battery is at %1' + , en: 'Your uploader battery is at %1' + , hr: 'Your uploader battery is at %1' + , de: 'Your uploader battery is at %1' + , dk: 'Your uploader battery is at %1' + , ko: 'Your uploader battery is at %1' + , nl: 'Your uploader battery is at %1' + , zh_cn: 'Your uploader battery is at %1' + , sv: 'Your uploader battery is at %1' + , fi: 'Your uploader battery is at %1' + , ro: 'Your uploader battery is at %1' + , pl: 'Your uploader battery is at %1' + , ru: 'Your uploader battery is at %1' + , tr: 'Your uploader battery is at %1' + }, + 'virtAsstLastLoop': { + bg: 'The last successful loop was %1' + , cs: 'Poslední úšpěšné provedení smyčky %1' + , en: 'The last successful loop was %1' + , hr: 'The last successful loop was %1' + , de: 'Der letzte erfolgreiche Loop war %1' + , dk: 'Seneste successfulde loop var %1' + , ko: 'The last successful loop was %1' + , nl: 'De meest recente goede loop was %1' + , zh_cn: '最后一次成功闭环的是在%1' + , sv: 'Senaste lyckade loop var %1' + , fi: 'Viimeisin onnistunut loop oli %1' + , ro: 'Ultima decizie loop implementată cu succes a fost %1' + , pl: 'Ostatnia pomyślna pętla była %1' + , ru: 'недавний успешный цикл был %1' + , tr: 'Son başarılı döngü %1 oldu' + }, + 'virtAsstLoopNotAvailable': { bg: 'Loop plugin does not seem to be enabled' , cs: 'Plugin smyčka není patrně povolený' , en: 'Loop plugin does not seem to be enabled' + , hr: 'Loop plugin does not seem to be enabled' , de: 'Das Loop Plugin scheint nicht aktiviert zu sein' , dk: 'Loop plugin lader ikke til at være slået til' + , ko: 'Loop plugin does not seem to be enabled' , nl: 'De Loop plugin is niet geactiveerd' + , zh_cn: 'Loop插件看起来没有被启用' , sv: 'Loop plugin verkar inte vara aktiverad' , fi: 'Loop plugin ei ole aktivoitu' , ro: 'Extensia loop pare a fi dezactivată' , pl: 'Plugin Loop prawdopodobnie nie jest włączona' + , ru: 'плагин ЗЦ Loop не активирован' + , tr: 'Döngü eklentisi etkin görünmüyor' + }, + 'virtAsstLoopForecastAround': { + bg: 'According to the loop forecast you are expected to be around %1 over the next %2' + , cs: 'Podle přepovědi smyčky je očekávána glykémie around %1 během následujících %2' + , en: 'According to the loop forecast you are expected to be around %1 over the next %2' + , hr: 'According to the loop forecast you are expected to be around %1 over the next %2' + , de: 'Entsprechend der Loop Vorhersage landest du bei around %1 während der nächsten %2' + , dk: 'Ifølge Loops forudsigelse forventes du at blive around %1 i den næste %2' + , ko: 'According to the loop forecast you are expected to be around %1 over the next %2' + , nl: 'Volgens de Loop voorspelling is je waarde around %1 over de volgnede %2' + , zh_cn: '根据loop的预测,在接下来的%2你的血糖将会是around %1' + , sv: 'Enligt Loops förutsägelse förväntas du bli around %1 inom %2' + , fi: 'Ennusteen mukaan olet around %1 seuraavan %2 ajan' + , ro: 'Potrivit previziunii date de loop se estiemază around %1 pentru următoarele %2' + , pl: 'Zgodnie z prognozą pętli, glikemia around %1 będzie podczas następnego %2' + , ru: 'по прогнозу алгоритма ЗЦ ожидается around %1 за последующие %2' + , tr: 'Döngü tahminine göre sonraki %2 ye göre around %1 olması bekleniyor' }, - 'alexaLoopForecast': { - bg: 'According to the loop forecast you are expected to be %1 over the next %2' - , cs: 'Podle přepovědi smyčky je očekávána glykémie %1 během následujících %2' - , en: 'According to the loop forecast you are expected to be %1 over the next %2' - , de: 'Entsprechend der Loop Vorhersage landest du bei %1 während der nächsten %2' - , dk: 'Ifølge Loops forudsigelse forventes du at blive %1 i den næste %2' - , nl: 'Volgens de Loop voorspelling is je waarde %1 over de volgnede %2' - , sv: 'Enligt Loops förutsägelse förväntas du bli %1 inom %2' - , fi: 'Ennusteen mukaan olet %1 seuraavan %2 ajan' - , ro: 'Potrivit previziunii date de loop se estiemază %1 pentru următoarele %2' - , pl: 'Zgodnie z prognozą pętli, glikemia %1 będzie podczas następnego %2' + 'virtAsstLoopForecastBetween': { + bg: 'According to the loop forecast you are expected to be between %1 and %2 over the next %3' + , cs: 'Podle přepovědi smyčky je očekávána glykémie between %1 and %2 během následujících %3' + , en: 'According to the loop forecast you are expected to be between %1 and %2 over the next %3' + , hr: 'According to the loop forecast you are expected to be between %1 and %2 over the next %3' + , de: 'Entsprechend der Loop Vorhersage landest du bei between %1 and %2 während der nächsten %3' + , dk: 'Ifølge Loops forudsigelse forventes du at blive between %1 and %2 i den næste %3' + , ko: 'According to the loop forecast you are expected to be between %1 and %2 over the next %3' + , nl: 'Volgens de Loop voorspelling is je waarde between %1 and %2 over de volgnede %3' + , zh_cn: '根据loop的预测,在接下来的%3你的血糖将会是between %1 and %2' + , sv: 'Enligt Loops förutsägelse förväntas du bli between %1 and %2 inom %3' + , fi: 'Ennusteen mukaan olet between %1 and %2 seuraavan %3 ajan' + , ro: 'Potrivit previziunii date de loop se estiemază between %1 and %2 pentru următoarele %3' + , pl: 'Zgodnie z prognozą pętli, glikemia between %1 and %2 będzie podczas następnego %3' + , ru: 'по прогнозу алгоритма ЗЦ ожидается between %1 and %2 за последующие %3' + , tr: 'Döngü tahminine göre sonraki %3 ye göre between %1 and %2 olması bekleniyor' }, - 'alexaForecastUnavailable': { + 'virtAsstAR2ForecastAround': { + bg: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , cs: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , en: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , hr: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , de: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , dk: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , ko: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , nl: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , zh_cn: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , sv: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , fi: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , ro: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , pl: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , ru: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + , tr: 'According to the AR2 forecast you are expected to be around %1 over the next %2' + }, + 'virtAsstAR2ForecastBetween': { + bg: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , cs: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , en: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , hr: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , de: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , dk: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , ko: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , nl: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , zh_cn: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , sv: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , fi: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , ro: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , pl: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , ru: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + , tr: 'According to the AR2 forecast you are expected to be between %1 and %2 over the next %3' + }, + 'virtAsstForecastUnavailable': { bg: 'Unable to forecast with the data that is available' , cs: 'S dostupnými daty přepověď není možná' , en: 'Unable to forecast with the data that is available' + , hr: 'Unable to forecast with the data that is available' , de: 'Mit den verfügbaren Daten ist eine Loop Vorhersage nicht möglich' , dk: 'Det er ikke muligt at forudsige md de tilgængelige data' + , ko: 'Unable to forecast with the data that is available' , nl: 'Niet mogelijk om een voorspelling te doen met de data die beschikbaar is' + , zh_cn: '血糖数据不可用,无法预测未来走势' , sv: 'Förutsägelse ej möjlig med tillgänlig data' , fi: 'Ennusteet eivät ole toiminnassa puuttuvan tiedon vuoksi' , ro: 'Estimarea este imposibilă pe baza datelor disponibile' , pl: 'Prognoza pętli nie jest możliwa, z dostępnymi danymi.' + , ru: 'прогноз при таких данных невозможен' + , tr: 'Mevcut verilerle tahmin edilemedi' }, - 'alexaRawBG': { - en: 'Your raw bg is %1' + 'virtAsstRawBG': { + en: 'Your raw bg is %1' , cs: 'Raw glykémie je %1' , de: 'Dein Rohblutzucker ist %1' , dk: 'Dit raw blodsukker er %1' + , ko: 'Your raw bg is %1' , nl: 'Je raw bloedwaarde is %1' + , zh_cn: '你的血糖是 %1' , sv: 'Ditt raw blodsocker är %1' , fi: 'Suodattamaton verensokeriarvo on %1' , ro: 'Glicemia brută este %1' , bg: 'Your raw bg is %1' + , hr: 'Your raw bg is %1' , pl: 'Glikemia RAW wynosi %1' + , ru: 'ваши необработанные данные RAW %1' + , tr: 'Ham kan şekeriniz %1' }, - 'alexaOpenAPSForecast': { + 'virtAsstOpenAPSForecast': { en: 'The OpenAPS Eventual BG is %1' , cs: 'OpenAPS Eventual BG je %1' , de: 'Der von OpenAPS vorhergesagte Blutzucker ist %1' , dk: 'OpenAPS forventet blodsukker er %1' + , ko: 'The OpenAPS Eventual BG is %1' , nl: 'OpenAPS uiteindelijke bloedglucose van %1' + , zh_cn: 'OpenAPS 预测最终血糖是 %1' , sv: 'OpenAPS slutgiltigt blodsocker är %1' , fi: 'OpenAPS verensokeriarvio on %1' , ro: 'Glicemia estimată de OpenAPS este %1' - ,bg: 'The OpenAPS Eventual BG is %1' + , bg: 'The OpenAPS Eventual BG is %1' + , hr: 'The OpenAPS Eventual BG is %1' , pl: 'Glikemia prognozowana przez OpenAPS wynosi %1' + , ru: 'OpenAPS прогнозирует ваш СК как %1 ' + , tr: 'OpenAPS tarafından tahmin edilen kan şekeri %1' + }, + 'virtAsstCob3person': { + bg: '%1 has $2 carbohydrates on board' + , cs: '%1 has $2 carbohydrates on board' + , de: '%1 has $2 carbohydrates on board' + , dk: '%1 has $2 carbohydrates on board' + , el: '%1 has $2 carbohydrates on board' + , en: '%1 has $2 carbohydrates on board' + , es: '%1 has $2 carbohydrates on board' + , fi: '%1 has $2 carbohydrates on board' + , fr: '%1 has $2 carbohydrates on board' + , he: '%1 has $2 carbohydrates on board' + , hr: '%1 has $2 carbohydrates on board' + , it: '%1 has $2 carbohydrates on board' + , ko: '%1 has $2 carbohydrates on board' + , nb: '%1 has $2 carbohydrates on board' + , nl: '%1 has $2 carbohydrates on board' + , pl: '%1 has $2 carbohydrates on board' + , pt: '%1 has $2 carbohydrates on board' + , ro: '%1 has $2 carbohydrates on board' + , ru: '%1 has $2 carbohydrates on board' + , sk: '%1 has $2 carbohydrates on board' + , sv: '%1 has $2 carbohydrates on board' + , tr: '%1 has $2 carbohydrates on board' + , zh_cn: '%1 has $2 carbohydrates on board' + , zh_tw: '%1 has $2 carbohydrates on board' }, - 'alexaCOB': { - en: '%1 %2 carbohydrates on board' - , cs: '%1 %2 aktivních sachridů' - , de: '%1 %2 Gramm Kohlenhydrate wirkend.' - , dk: '%1 %2 gram aktive kulhydrater' - , nl: '%1 %2 actieve koolhydraten' - , sv: '%1 %2 gram aktiva kolhydrater' - , fi: '%1 %2 aktiivista hiilihydraattia' - , ro: '%1 %2 carbohidrați activi în corp' - ,bg: '%1 %2 carbohydrates on board' - , pl: '%1 %2 aktywnych węglowodanów' + 'virtAsstCob': { + bg: 'You have %1 carbohydrates on board' + , cs: 'You have %1 carbohydrates on board' + , de: 'You have %1 carbohydrates on board' + , dk: 'You have %1 carbohydrates on board' + , el: 'You have %1 carbohydrates on board' + , en: 'You have %1 carbohydrates on board' + , es: 'You have %1 carbohydrates on board' + , fi: 'You have %1 carbohydrates on board' + , fr: 'You have %1 carbohydrates on board' + , he: 'You have %1 carbohydrates on board' + , hr: 'You have %1 carbohydrates on board' + , it: 'You have %1 carbohydrates on board' + , ko: 'You have %1 carbohydrates on board' + , nb: 'You have %1 carbohydrates on board' + , nl: 'You have %1 carbohydrates on board' + , pl: 'You have %1 carbohydrates on board' + , pt: 'You have %1 carbohydrates on board' + , ro: 'You have %1 carbohydrates on board' + , ru: 'You have %1 carbohydrates on board' + , sk: 'You have %1 carbohydrates on board' + , sv: 'You have %1 carbohydrates on board' + , tr: 'You have %1 carbohydrates on board' + , zh_cn: 'You have %1 carbohydrates on board' + , zh_tw: 'You have %1 carbohydrates on board' + }, + 'virtAsstUnknownIntentTitle': { + en: 'Unknown Intent' + , cs: 'Unknown Intent' + , de: 'Unknown Intent' + , dk: 'Unknown Intent' + , ko: 'Unknown Intent' + , nl: 'Unknown Intent' + , zh_cn: 'Unknown Intent' + , sv: 'Unknown Intent' + , fi: 'Unknown Intent' + , ro: 'Unknown Intent' + , bg: 'Unknown Intent' + , hr: 'Unknown Intent' + , pl: 'Unknown Intent' + , ru: 'Unknown Intent' + , tr: 'Unknown Intent' + }, + 'virtAsstUnknownIntentText': { + en: 'I\'m sorry, I don\'t know what you\'re asking for.' + , cs: 'I\'m sorry, I don\'t know what you\'re asking for.' + , de: 'I\'m sorry, I don\'t know what you\'re asking for.' + , dk: 'I\'m sorry, I don\'t know what you\'re asking for.' + , ko: 'I\'m sorry, I don\'t know what you\'re asking for.' + , nl: 'I\'m sorry, I don\'t know what you\'re asking for.' + , zh_cn: 'I\'m sorry, I don\'t know what you\'re asking for.' + , sv: 'I\'m sorry, I don\'t know what you\'re asking for.' + , fi: 'I\'m sorry, I don\'t know what you\'re asking for.' + , ro: 'I\'m sorry, I don\'t know what you\'re asking for.' + , bg: 'I\'m sorry, I don\'t know what you\'re asking for.' + , hr: 'I\'m sorry, I don\'t know what you\'re asking for.' + , pl: 'I\'m sorry, I don\'t know what you\'re asking for.' + , ru: 'I\'m sorry, I don\'t know what you\'re asking for.' + , tr: 'I\'m sorry, I don\'t know what you\'re asking for.' }, 'Fat [g]': { cs: 'Tuk [g]' @@ -12303,13 +14111,18 @@ function init() { ,es: 'Grasas [g]' ,fi: 'Rasva [g]' ,fr: 'Graisses [g]' + ,ko: 'Fat [g]' ,nl: 'Vet [g]' + ,zh_cn: '脂肪[g]' ,ro: 'Grăsimi [g]' - ,ru: 'жиры' + ,ru: 'жиры [g]' ,it: 'Grassi [g]' ,sv: 'Fett [g]' ,bg: 'Мазнини [гр]' + ,hr: 'Masnoće [g]' ,pl: 'Tłuszcz [g]' + ,tr: 'Yağ [g]' + ,he: '[g] שמן' }, 'Protein [g]': { cs: 'Proteiny [g]' @@ -12318,13 +14131,18 @@ function init() { ,es: 'Proteina [g]' ,fi: 'Proteiini [g]' ,fr: 'Protéines [g]' + ,ko: 'Protein [g]' ,nl: 'Proteine [g]' + ,zh_cn: '蛋白质[g]' ,ro: 'Proteine [g]' - ,ru: 'белки' + ,ru: 'белки [g]' ,it: 'Proteine [g]' ,sv: 'Protein [g]' ,bg: 'Протеини [гр]' + ,hr: 'Proteini [g]' ,pl: 'Białko [g]' + ,tr: 'Protein [g]' + ,he: '[g] חלבון' }, 'Energy [kJ]': { cs: 'Energie [kJ]' @@ -12334,58 +14152,82 @@ function init() { ,es: 'Energía [Kj]' ,fr: 'Énergie [kJ]' ,ro: 'Energie [g]' - ,ru: 'энергетика' + ,ru: 'энергия [kJ]' ,it: 'Energia [kJ]' + ,zh_cn: '能量 [kJ]' + ,ko: 'Energy [kJ]' ,nl: 'Energie [kJ]' ,sv: 'Energi [kJ]' ,bg: 'Енергия [kJ]' + ,hr: 'Energija [kJ]' ,pl: 'Energia [kJ}' + ,tr: 'Enerji [kJ]' + ,he: '[kJ] אנרגיה' }, 'Clock Views:': { cs: 'Hodiny:' ,fi: 'Kellonäkymä:' + ,ko: '시계 보기' ,nl: 'Klokweergave:' ,es: 'Vista del reloj:' ,fr: 'Vue Horloge:' ,ro: 'Vedere tip ceas:' ,ru: 'цифры крупно:' ,it: 'Vista orologio:' + ,zh_cn: '时钟视图' ,sv: 'Visa klocka:' ,de: 'Uhr-Anzeigen' ,dk: 'Vis klokken:' ,bg: 'Часовник изглед:' + ,hr: 'Satovi:' ,pl: 'Widoki zegarów' + ,tr: 'Saat Görünümü' + ,he: 'צגים השעון' }, 'Clock': { cs: 'Hodiny' ,fr: 'L\'horloge' + ,ko: '시계모드' ,nl: 'Klok' - ,sv: 'Klocka' + ,zh_cn: '时钟' + ,sv: 'Klocka' ,de: 'Uhr' ,dk: 'Klokken' ,fi: 'Kello' ,ro: 'Ceas' ,it: 'Orologio' ,bg: 'Часовник' + ,hr: 'Sat' ,pl: 'Zegar' + ,ru: 'часы' + ,tr: 'Saat' + ,he: 'שעון' }, 'Color': { cs: 'Barva' ,fr: 'Couleur' + ,ko: '색상모드' ,nl: 'Kleur' - ,sv: 'Färg' + ,zh_cn: '彩色' + ,sv: 'Färg' ,de: 'Farbe' ,dk: 'Farve' ,fi: 'Väri' ,ro: 'Culoare' ,it: 'Colore' ,bg: 'Цвят' + ,hr: 'Boja' ,pl: 'Kolor' + ,ru: 'цвет' + ,tr: 'Renk' + ,he: 'צבע' }, 'Simple': { cs: 'Jednoduchý' ,fr: 'Simple' + ,ko: '간편 모드' ,nl: 'Simpel' + ,zh_cn: '简单' ,sv: 'Simpel' ,de: 'Einfach' ,dk: 'Simpel' @@ -12393,166 +14235,260 @@ function init() { ,ro: 'Simplu' ,it: 'Semplice' ,bg: 'Прост' + ,hr: 'Jednostavan' ,pl: 'Prosty' + ,ru: 'простой' + ,tr: 'Basit' + ,he: 'פשוט' }, 'TDD average': { cs: 'Průměrná denní dávka' , fi: 'Päivän kokonaisinsuliinin keskiarvo' + , ko: 'TDD average' , nl: 'Gemiddelde dagelijkse insuline (TDD)' + ,zh_cn: '日胰岛素用量平均值' , sv: 'Genomsnittlig daglig mängd insulin' , de: 'durchschnittliches Insulin pro Tag (TDD)' , dk: 'Gennemsnitlig daglig mængde insulin' , ro: 'Media Dozei Zilnice Totale de insulină (TDD)' , it: 'Totale Dose Giornaliera media (TDD)' , bg: 'Обща дневна доза средно' + , hr: 'Srednji TDD' , pl: 'Średnia dawka dzienna' + , ru: 'средняя суточная доза инсулина' + , tr: 'Ortalama günlük Toplam Doz (TDD)' }, 'Carbs average': { cs: 'Průměrné množství sacharidů' , fi: 'Hiilihydraatit keskiarvo' + , ko: 'Carbs average' , nl: 'Gemiddelde koolhydraten per dag' + ,zh_cn: '碳水化合物平均值' , sv: 'Genomsnittlig mängd kolhydrater per dag' , de: 'durchschnittliche Kohlenhydrate pro Tag' , dk: 'Gennemsnitlig mængde kulhydrater per dag' , ro: 'Media carbohidraților' , it: 'Media carboidrati' , bg: 'Въглехидрати средно' + , hr: 'Prosjek UGH' , pl: 'Średnia ilość węglowodanów' + , ru: 'среднее кол-во углеводов за сутки' + , tr: 'Günde ortalama karbonhidrat' + ,he: 'פחמימות ממוצע' }, 'Eating Soon': { cs: 'Blížící se jídlo' , fi: 'Ruokailu pian' + , ko: 'Eating Soon' , nl: 'Pre-maaltijd modus' + ,zh_cn: '过会吃饭' , sv: 'Äter snart' , de: 'Bald Essen' , dk: 'Spiser snart' , ro: 'Mâncare în curând' , it: 'Mangiare presto' , bg: 'Преди хранене' + , hr: 'Uskoro obrok' , pl: 'Przed jedzeniem' + , ru: 'скоро прием пищи' + , tr: 'Yakında Yenecek' + , he: 'אוכל בקרוב' }, 'Last entry {0} minutes ago': { cs: 'Poslední hodnota {0} minut zpět' , fi: 'Edellinen verensokeri {0} minuuttia sitten' + , ko: 'Last entry {0} minutes ago' , nl: 'Laatste waarde {0} minuten geleden' + ,zh_cn: '最后一个条目 {0} 分钟之前' , sv: 'Senaste värde {0} minuter sedan' , de: 'Letzter Eintrag vor {0} Minuten' , dk: 'Seneste værdi {0} minutter siden' , ro: 'Ultima înregistrare acum {0} minute' , it: 'Ultimo inserimento {0} minuti fa' , bg: 'Последен запис преди {0} минути' + , hr: 'Posljednji zapis prije {0} minuta' , pl: 'Ostatni wpis przed {0} minutami' + , ru: 'предыдущая запись {0} минут назад' + , tr: 'Son giriş {0} dakika önce' }, 'change': { cs: 'změna' , fi: 'muutos' + , ko: 'change' , nl: 'wijziging' + ,zh_cn: '改变' , sv: 'byta' , de: 'verändern' , dk: 'ændre' , ro: 'schimbare' , it: 'cambio' , bg: 'промяна' + , hr: 'promjena' , pl: 'zmiana' + , ru: 'замена' + , tr: 'değişiklik' + , he: 'שינוי' }, 'Speech': { cs: 'Hlas' , fi: 'Puhe' + , ko: 'Speech' , nl: 'Spraak' + ,zh_cn: '朗读' , sv: 'Tal' , de: 'Sprache' , dk: 'Tale' , ro: 'Vorbă' , it: 'Voce' , bg: 'Глас' + , hr: 'Govor' , pl: 'Głos' + , ru: 'речь' + , tr: 'Konuş' + , he: 'דיבור' }, 'Target Top': { cs: 'Horní cíl' , dk: 'Højt mål' , fi: 'Tavoite ylä' + , ko: 'Target Top' , nl: 'Hoog tijdelijk doel' , ro: 'Țintă superioară' , it: 'Limite superiore' + ,zh_cn: '目标高值' , sv: 'Högt målvärde' , bg: 'Горна граница' + , hr: 'Gornja granica' , pl: 'Górny limit' + , ru: 'верхняя граница цели' + , de: 'Oberes Ziel' + , tr: 'Hedef Üst' + , he: 'ראש היעד' }, 'Target Bottom': { cs: 'Dolní cíl' , dk: 'Lavt mål' , fi: 'Tavoite ala' + , ko: 'Target Bottom' , nl: 'Laag tijdelijk doel' + ,zh_cn: '目标低值' , ro: 'Țintă inferioară' , it: 'Limite inferiore' , sv: 'Lågt målvärde' , bg: 'Долна граница' + , hr: 'Donja granica' , pl: 'Dolny limit' + , ru: 'нижняя граница цели' + , de: 'Unteres Ziel' + , tr: 'Hedef Alt' + , he: 'תחתית היעד' }, 'Canceled': { cs: 'Zrušený' , dk: 'Afbrudt' , fi: 'Peruutettu' + , ko: 'Canceled' , nl: 'Geannuleerd' + ,zh_cn: '被取消了' , ro: 'Anulat' , it: 'Cancellato' , sv: 'Avbruten' , bg: 'Отказан' + , hr: 'Otkazano' , pl: 'Anulowane' + , ru: 'отменено' + , de: 'Abgebrochen' + , tr: 'İptal edildi' + , he: 'מבוטל' }, 'Meter BG': { cs: 'Hodnota z glukoměru' , dk: 'Blodsukkermåler BS' , fi: 'Mittarin VS' + , ko: 'Meter BG' , nl: 'Bloedglucosemeter waarde' + ,zh_cn: '指血血糖值' , ro: 'Glicemie din glucometru' , it: 'Glicemia Capillare' , sv: 'Blodsockermätare BG' , bg: 'Измерена КЗ' + , hr: 'GUK iz krvi' , pl: 'Glikemia z krwi' + , ru: 'ГК по глюкометру' + , de: 'Wert Blutzuckermessgerät' + , tr: 'Glikometre KŞ' + , he: 'סוכר הדם של מד' }, 'predicted': { cs: 'přepověď' , dk: 'forudset' , fi: 'ennuste' + , ko: 'predicted' , nl: 'verwachting' + ,zh_cn: '预测' , ro: 'estimat' , it: 'predetto' , sv: 'prognos' ,bg: 'прогнозна' + , hr: 'prognozirano' , pl: 'prognoza' + , ru: 'прогноз' + , de: 'vorhergesagt' + , tr: 'tahmin' + , he: 'חזה' }, 'future': { cs: 'budoucnost' , dk: 'fremtidige' , fi: 'tulevaisuudessa' + , ko: 'future' , nl: 'toekomstig' + ,zh_cn: '将来' , ro: 'viitor' , it: 'futuro' , sv: 'framtida' , bg: 'бъдеще' + , hr: 'budućnost' , pl: 'przyszłość' + , ru: 'будущее' + , de: 'Zukunft' + , tr: 'gelecek' + , he: 'עתיד' }, 'ago': { cs: 'zpět' , dk: 'siden' , fi: 'sitten' + , ko: 'ago' , nl: 'geleden' + ,zh_cn: '之前' , ro: 'în trecut' , sv: 'förfluten' , bg: 'преди' + , hr: 'prije' , pl: 'temu' + , ru: 'в прошлом' + , de: 'vor' + , tr: 'önce' + , he: 'לפני' }, 'Last data received': { cs: 'Poslední data přiajata' , dk: 'Sidste data modtaget' , fi: 'Tietoa vastaanotettu viimeksi' + , ko: 'Last data received' , nl: 'Laatste gegevens ontvangen' + ,zh_cn: '上次收到数据' , ro: 'Ultimele date primite' , it: 'Ultimo dato ricevuto' , sv: 'Data senast mottagen' , bg: 'Последни данни преди' + , hr: 'Podaci posljednji puta primljeni' , pl: 'Ostatnie otrzymane dane' + , ru: 'последние данные получены' + , de: 'Zuletzt Daten empfangen' + , tr: 'Son veri alındı' + , he: 'הנתונים המקבל אחרונים' }, 'Clock View': { cs: 'Hodiny' @@ -12561,14 +14497,75 @@ function init() { ,es: 'Vista del reloj' ,fr: 'Vue Horloge' ,ro: 'Vedere tip ceas' - ,ru: 'цифры крупно' + ,ru: 'вид циферблата' + ,ko: 'Clock View' ,it: 'Vista orologio' ,sv: 'Visa klocka' ,bg: 'Изглед часовник' + ,hr: 'Prikaz sata' ,nl: 'Klokweergave' + ,zh_cn: '时钟视图' ,de: 'Uhr-Anzeigen' - , pl: 'Widok zegara' - } + ,pl: 'Widok zegara' + ,tr: 'Saat Görünümü' + ,he: 'צג השעון' + }, + 'Protein': { + fi: 'Proteiini' + ,de: 'Protein' + ,tr: 'Protein' + ,hr: 'Proteini' + ,ru: 'Белки' + ,he: 'חלבון' + }, + 'Fat': { + fi: 'Rasva' + ,de: 'Fett' + ,tr: 'Yağ' + ,hr: 'Masti' + ,ru: 'Жиры' + ,he: 'שמן' + }, + 'Protein average': { + fi: 'Proteiini keskiarvo' + ,de: 'Proteine Durchschnitt' + ,tr: 'Protein Ortalaması' + ,hr: 'Prosjek proteina' + ,ru: 'Средний белок' + ,he: 'חלבון ממוצע' + }, + 'Fat average': { + fi: 'Rasva keskiarvo' + ,de: 'Fett Durchschnitt' + ,tr: 'Yağ Ortalaması' + ,hr: 'Prosjek masti' + ,ru: 'Средний жир' + ,he: 'שמן ממוצע' + }, + 'Total carbs': { + fi: 'Hiilihydraatit yhteensä' + , de: 'Kohlenhydrate gesamt' + ,tr: 'Toplam Karbonhidrat' + ,hr: 'Ukupno ugh' + ,ru: 'Всего углеводов' + ,he: 'כל פחמימות' + }, + 'Total protein': { + fi: 'Proteiini yhteensä' + , de: 'Protein gesamt' + ,tr: 'Toplam Protein' + ,hr: 'Ukupno proteini' + ,ru: 'Всего белков' + ,he: 'כל חלבונים' + }, + 'Total fat': { + fi: 'Rasva yhteensä' + , de: 'Fett gesamt' + ,tr: 'Toplam Yağ' + ,hr: 'Ukupno masti' + ,ru: 'Всего жиров' + ,he: 'כל שומנים' + } }; language.translations = translations; @@ -12603,6 +14600,7 @@ function init() { } if (options && options.params) { for (var i = 0; i < options.params.length; i++) { + // eslint-disable-next-line no-useless-escape var r = new RegExp('\%' + (i+1), 'g'); translated = translated.replace(r, options.params[i]); } @@ -12626,7 +14624,7 @@ function init() { language.lang = newlang; language.languages.forEach(function (l) { - if (l.code == language.lang && l.speechCode) language.speechCode = l.speechCode; + if (l.code === language.lang && l.speechCode) language.speechCode = l.speechCode; }); return language(); diff --git a/lib/notifications.js b/lib/notifications.js index 8eb1f4eb042..1a03ab87244 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -15,7 +15,7 @@ var Alarm = function(level, group, label) { }; // list of alarms with their thresholds -var alarms = { }; +var alarms = {}; function init (env, ctx) { function notifications () { @@ -41,7 +41,7 @@ function init (env, ctx) { var sendClear = false; - for (var level = 1; level <=2; level++) { + for (var level = 1; level <= 2; level++) { var alarm = getAlarm(level, group); if (alarm.lastEmitTime) { console.info('auto acking ' + alarm.level, ' - ', group); @@ -51,7 +51,7 @@ function init (env, ctx) { } if (sendClear) { - var notify = {clear: true, title: 'All Clear', message: 'Auto ack\'d alarm(s)', group: group}; + var notify = { clear: true, title: 'All Clear', message: 'Auto ack\'d alarm(s)', group: group }; ctx.bus.emit('notification', notify); logEmitEvent(notify); } @@ -70,8 +70,8 @@ function init (env, ctx) { var requests = {}; - notifications.initRequests = function initRequests ( ) { - requests = { notifies: [] , snoozes: []}; + notifications.initRequests = function initRequests () { + requests = { notifies: [], snoozes: [] }; }; notifications.initRequests(); @@ -82,12 +82,12 @@ function init (env, ctx) { */ notifications.findHighestAlarm = function findHighestAlarm (group) { group = group || 'default'; - var filtered = _.filter(requests.notifies, {group: group}); - return _.find(filtered, {level: levels.URGENT}) || _.find(filtered, {level: levels.WARN}); + var filtered = _.filter(requests.notifies, { group: group }); + return _.find(filtered, { level: levels.URGENT }) || _.find(filtered, { level: levels.WARN }); }; - notifications.findUnSnoozeable = function findUnSnoozeable ( ) { - return _.filter(requests.notifies, function (notify) { + notifications.findUnSnoozeable = function findUnSnoozeable () { + return _.filter(requests.notifies, function(notify) { return notify.level <= levels.INFO || notify.isAnnouncement; }); }; @@ -95,7 +95,7 @@ function init (env, ctx) { notifications.snoozedBy = function snoozedBy (notify) { if (notify.isAnnouncement) { return false; } - var filtered = _.filter(requests.snoozes, {group: notify.group}); + var filtered = _.filter(requests.snoozes, { group: notify.group }); if (_.isEmpty(filtered)) { return false; } @@ -107,13 +107,8 @@ function init (env, ctx) { return _.last(sorted); }; - notifications.registerGroup = function registerGroup (group) { - if (groups.indexOf(group) < 0) { - groups.push(group); - } - }; - notifications.requestNotify = function requestNotify (notify) { + // eslint-disable-next-line no-prototype-builtins if (!notify.hasOwnProperty('level') || !notify.title || !notify.message || !notify.plugin) { console.error(new Error('Unable to request notification, since the notify isn\'t complete: ' + JSON.stringify(notify))); return; @@ -135,7 +130,7 @@ function init (env, ctx) { requests.snoozes.push(snooze); }; - notifications.process = function process ( ) { + notifications.process = function process () { var notifyGroups = _.map(requests.notifies, function eachNotify (notify) { return notify.group; @@ -205,7 +200,7 @@ function init (env, ctx) { }; - function ifTestModeThen(callback) { + function ifTestModeThen (callback) { if (env.testMode) { return callback(); } else { @@ -213,15 +208,15 @@ function init (env, ctx) { } } - notifications.resetStateForTests = function resetStateForTests ( ) { - ifTestModeThen(function doResetStateForTests ( ) { + notifications.resetStateForTests = function resetStateForTests () { + ifTestModeThen(function doResetStateForTests () { console.info('resetting notifications state for tests'); alarms = {}; }); }; notifications.getAlarmForTests = function getAlarmForTests (level, group) { - return ifTestModeThen(function doResetStateForTests ( ) { + return ifTestModeThen(function doResetStateForTests () { group = group || 'default'; var alarm = getAlarm(level, group); console.info('got alarm for tests: ', alarm); @@ -249,7 +244,7 @@ function init (env, ctx) { }; } - function logEmitEvent(notify) { + function logEmitEvent (notify) { var type = notify.level >= levels.WARN ? 'ALARM' : (notify.clear ? 'ALL CLEAR' : 'NOTIFICATION'); console.info([ logTimestamp() + '\tEMITTING ' + type + ':' @@ -257,7 +252,7 @@ function init (env, ctx) { ].join('\n')); } - function logSnoozingEvent(highestAlarm, snoozedBy) { + function logSnoozingEvent (highestAlarm, snoozedBy) { console.info([ logTimestamp() + '\tSNOOZING ALARM:' , ' ' + JSON.stringify(notifyToView(highestAlarm)) @@ -267,11 +262,11 @@ function init (env, ctx) { } //TODO: we need a common logger, but until then... - function logTimestamp ( ) { + function logTimestamp () { return (new Date).toISOString(); } return notifications(); } -module.exports = init; \ No newline at end of file +module.exports = init; diff --git a/lib/plugins/alexa-plugin.md b/lib/plugins/alexa-plugin.md deleted file mode 100644 index ab2c897c66e..00000000000 --- a/lib/plugins/alexa-plugin.md +++ /dev/null @@ -1,327 +0,0 @@ -Nightscout Alexa Plugin -====================================== - -## Overview - -To add Alexa support for your Nightscout site, here's what you need to do: - -1. [Activate the `alexa` plugin](#activate-the-nightscout-alexa-plugin) on your Nightscout site, so your site will respond correctly to Alexa's requests. -1. [Create a custom Alexa skill](#create-your-alexa-skill) that points at your site and defines certain questions you want to be able to ask. (You'll copy and paste a basic template for this, to keep things simple.) - -To add Alexa support for a plugin, [check this out](#adding-alexa-support-to-a-plugin). - -## Activate the Nightscout Alexa Plugin - -1. Your Nightscout site needs to be new enough that it supports the `alexa` plugin. It needs to be [version 0.9.1 (Grilled Cheese)](https://github.com/nightscout/cgm-remote-monitor/releases/tag/0.9.1) or later. See [updating my version](https://github.com/nightscout/cgm-remote-monitor#updating-my-version) if you need a newer version. -1. Add `alexa` to the list of plugins in your `ENABLE` setting. ([Environment variables](https://github.com/nightscout/cgm-remote-monitor#environment) are set in the configuration section for your monitor. Typically Azure, Heroku, etc.) - -## Create Your Alexa Skill - -### Get an Amazon Developer account - -- Sign up for a free [Amazon Developer account](https://developer.amazon.com/) if you don't already have one. -- [Register](https://developer.amazon.com/docs/devconsole/test-your-skill.html#h2_register) your Alexa-enabled device with your Developer account. -- Sign in and go to the [Alexa developer portal](https://developer.amazon.com/alexa). - -### Create a new Alexa skill - -1. Select "Alexa Skills Kit" in the main menu bar. -1. Click the "Start a Skill" button. This will take you to the Skills console. -1. Click the "Create Skill" button on the Skills console page. -1. Name your new skill "Nightscout" (or something else, if you like). Use English (US) as your language. Click "Next". -1. Choose a model to add to your skill. Click "Select" under "Custom" model, then click "Create skill" on the upper right. -1. Congrats! Your empty custom skill should now be created. - -### Define the interaction model - -Your Alexa skill's "interaction model" defines how your spoken questions get translated into requests to your Nightscout site, and how your Nightscout site's responses get translated into the audio responses that Alexa says back to you. - -To get up and running with a basic interaction model, which will allow you to ask Alexa a few basic questions about your Nightscout site, you can copy and paste the configuration code below. - -```json -{ - "interactionModel": { - "languageModel": { - "invocationName": "nightscout", - "intents": [ - { - "name": "NSStatus", - "slots": [], - "samples": [ - "How am I doing" - ] - }, - { - "name": "UploaderBattery", - "slots": [], - "samples": [ - "How is my uploader battery" - ] - }, - { - "name": "PumpBattery", - "slots": [], - "samples": [ - "How is my pump battery" - ] - }, - { - "name": "LastLoop", - "slots": [], - "samples": [ - "When was my last loop" - ] - }, - { - "name": "MetricNow", - "slots": [ - { - "name": "metric", - "type": "LIST_OF_METRICS" - }, - { - "name": "pwd", - "type": "AMAZON.US_FIRST_NAME" - } - ], - "samples": [ - "What is my {metric}", - "What my {metric} is", - "What is {pwd} {metric}" - ] - }, - { - "name": "InsulinRemaining", - "slots": [ - { - "name": "pwd", - "type": "AMAZON.US_FIRST_NAME" - } - ], - "samples": [ - "How much insulin do I have left", - "How much insulin do I have remaining", - "How much insulin does {pwd} have left", - "How much insulin does {pwd} have remaining" - ] - } - ], - "types": [ - { - "name": "LIST_OF_METRICS", - "values": [ - { - "name": { - "value": "bg" - } - }, - { - "name": { - "value": "blood glucose" - } - }, - { - "name": { - "value": "number" - } - }, - { - "name": { - "value": "iob" - } - }, - { - "name": { - "value": "insulin on board" - } - }, - { - "name": { - "value": "current basal" - } - }, - { - "name": { - "value": "basal" - } - }, - { - "name": { - "value": "cob" - } - }, - { - "name": { - "value": "carbs on board" - } - }, - { - "name": { - "value": "carbohydrates on board" - } - }, - { - "name": { - "value": "loop forecast" - } - }, - { - "name": { - "value": "ar2 forecast" - } - }, - { - "name": { - "value": "forecast" - } - }, - { - "name": { - "value": "raw bg" - } - }, - { - "name": { - "value": "raw blood glucose" - } - } - ] - } - ] - } - } -} -``` - -Select "JSON Editor" in the left-hand menu on your skill's edit page (which you should be on if you followed the above instructions). Replace everything in the textbox with the above code. Then click "Save Model" at the top. A success message should appear indicating that the model was saved. - -Next you need to build your custom model. Click "Build Model" at the top of the same page. It'll take a minute to build, and then you should see another success message, "Build Successful". - -You now have a custom Alexa skill that knows how to talk to a Nightscout site. - -### Point your skill at your site - -Now you need to point your skill at *your* Nightscout site. - -1. In the left-hand menu for your skill, there's an option called "Endpoint". Click it. -1. Under "Service Endpoint Type", select "HTTPS". -1. You only need to set up the Default Region. In the box that says "Enter URI...", put in `https://{yourdomain}/api/v1/alexa`. (So if your Nightscout site is at `mynightscoutsite.herokuapp.com`, you'll enter `https://mynightscoutsite.herokuapp.com/api/v1/alexa` in the box.) -1. In the dropdown under the previous box, select "My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority". -1. Click the "Save Endpoints" button at the top. -1. You should see a success message pop up when the save succeeds. - -### Test your skill out with the test tool - -Click on the "Test" tab on the top menu. This will take you to the page where you can test your new skill. - -Enable testing for your skill (click the toggle). As indicated on this page, when testing is enabled, you can interact with the development version of your skill in the Alexa simulator and on all devices linked to your Alexa developer account. (Your skill will always be a development version. There's no need to publish it to the public.) - -After you enable testing, you can also use the Alexa Simulator in the left column, to try out the skill. You can type in questions and see the text your skill would reply with. You can also hold the microphone icon to ask questions using your microphone, and you'll get the audio and text responses back. - -##### What questions can you ask it? - -*Forecast:* - -- "Alexa, ask Nightscout how am I doing" -- "Alexa, ask Nightscout how I'm doing" - -*Uploader Battery:* - -- "Alexa, ask Nightscout how is my uploader battery" - -*Pump Battery:* - -- "Alexa, ask Nightscout how is my pump battery" - -*Metrics:* - -- "Alexa, ask Nightscout what my bg is" -- "Alexa, ask Nightscout what my blood glucose is" -- "Alexa, ask Nightscout what my number is" -- "Alexa, ask Nightscout what is my insulin on board" -- "Alexa, ask Nightscout what is my basal" -- "Alexa, ask Nightscout what is my current basal" -- "Alexa, ask Nightscout what is my cob" -- "Alexa, ask Nightscout what is Charlie's carbs on board" -- "Alexa, ask Nightscout what is Sophie's carbohydrates on board" -- "Alexa, ask Nightscout what is Harper's loop forecast" -- "Alexa, ask Nightscout what is Alicia's ar2 forecast" -- "Alexa, ask Nightscout what is Peter's forecast" -- "Alexa, ask Nightscout what is Arden's raw bg" -- "Alexa, ask Nightscout what is Dana's raw blood glucose" - -*Insulin Remaining:* - -- "Alexa, ask Nightscout how much insulin do I have left" -- "Alexa, ask Nightscout how much insulin do I have remaining" -- "Alexa, ask Nightscout how much insulin does Dana have left? -- "Alexa, ask Nightscout how much insulin does Arden have remaining? - -*Last Loop:* - -- "Alexa, ask Nightscout when was my last loop" - -(Note: all the formats with specific names will respond to questions for any first name. You don't need to configure anything with your PWD's name.) - -### Activate the skill on your Echo or other device - -If your device is [registered](https://developer.amazon.com/docs/devconsole/test-your-skill.html#h2_register) with your developer account, you should be able to use your skill right away. Try it by asking Alexa one of the above questions using your device. - -## Adding Alexa support to a plugin - -This document assumes some familiarity with the Alexa interface. You can find more information [here](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/getting-started-guide). - -To add alexa support to a plugin the ``init`` should return an object that contains an "alexa" key. Here is an example: - -```javascript -var iob = { - name: 'iob' - , label: 'Insulin-on-Board' - , pluginType: 'pill-major' - , alexa : { - rollupHandlers: [{ - rollupGroup: "Status" - , rollupName: "current iob" - , rollupHandler: alexaIOBRollupHandler - }] - , intentHandlers: [{ - intent: "MetricNow" - , routableSlot: "metric" - , slots: ["iob", "insulin on board"] - , intentHandler: alexaIOBIntentHandler - }] - } -}; -``` - -There are 2 types of handlers that you will need to supply: -* Intent handler - enables you to "teach" Alexa how to respond to a user's question. -* A rollup handler - enables you to create a command that aggregates information from multiple plugins. This would be akin to the Alexa "flash briefing". An example would be a status report that contains your current bg, iob, and your current basal. - -### Intent Handlers - -A plugin can expose multiple intent handlers. -+ ``intent`` - this is the intent in the "intent schema" above -+ ``routeableSlot`` - This enables routing by a slot name to the appropriate intent handler for overloaded intents e.g. "What is my " - iob, bg, cob, etc. This value should match the slot named in the "intent schema" -+ ``slots`` - These are the values of the slots. Make sure to add these values to the appropriate custom slot -+ ``intenthandler`` - this is a callback function that receives 3 arguments - - ``callback`` Call this at the end of your function. It requires 2 arguments - - ``title`` - Title of the handler. This is the value that will be displayed on the Alexa card - - ``text`` - This is text that Alexa should speak. - - ``slots`` - these are the slots that Alexa detected - - ``sandbox`` - This is the nightscout sandbox that allows access to various functions. - -### Rollup handlers - -A plugin can also expose multiple rollup handlers -+ ``rollupGroup`` - This is the key that is used to aggregate the responses when the intent is invoked -+ ``rollupName`` - This is the name of the handler. Primarily used for debugging -+ ``rollupHandler`` - this is a callback function that receives 3 arguments - - ``slots`` - These are the values of the slots. Make sure to add these values to the appropriate custom slot - - ``sandbox`` - This is the nightscout sandbox that allows access to various functions. - - ``callback`` - - - ``error`` - This would be an error message - - ``response`` - A simple object that expects a ``results`` string and a ``priority`` integer. Results should be the text (speech) that is added to the rollup and priority affects where in the rollup the text should be added. The lowest priority is spoken first. An example callback: - ```javascript - callback(null, {results: "Hello world", priority: 1}); - ``` diff --git a/lib/plugins/alexa.js b/lib/plugins/alexa.js index 38ae449c249..da7bbdca2f1 100644 --- a/lib/plugins/alexa.js +++ b/lib/plugins/alexa.js @@ -1,60 +1,58 @@ var _ = require('lodash'); var async = require('async'); -function init(env, ctx) { - console.log('Configuring Alexa.'); +function init (env, ctx) { + console.log('Configuring Alexa...'); function alexa() { return alexa; } var intentHandlers = {}; var rollup = {}; - // This configures a router/handler. A routable slot the name of a slot that you wish to route on and the slotValues - // are the values that determine the routing. This allows for specific intent handlers based on the value of a - // specific slot. Routing is only supported on one slot for now. - // There is no protection for a previously configured handler - one plugin can overwrite the handler of another - // plugin. - alexa.configureIntentHandler = function configureIntentHandler(intent, handler, routableSlot, slotValues) { - if (! intentHandlers[intent]) { + // There is no protection for a previously handled metric - one plugin can overwrite the handler of another plugin. + alexa.configureIntentHandler = function configureIntentHandler(intent, handler, metrics) { + if (!intentHandlers[intent]) { intentHandlers[intent] = {}; } - if (routableSlot && slotValues) { - for (var i = 0, len = slotValues.length; i < len; i++) { - if (! intentHandlers[intent][routableSlot]) { - intentHandlers[intent][routableSlot] = {}; + if (metrics) { + for (var i = 0, len = metrics.length; i < len; i++) { + if (!intentHandlers[intent][metrics[i]]) { + intentHandlers[intent][metrics[i]] = {}; } - if (!intentHandlers[intent][routableSlot][slotValues[i]]) { - intentHandlers[intent][routableSlot][slotValues[i]] = {}; - } - intentHandlers[intent][routableSlot][slotValues[i]].handler = handler; + console.log('Storing handler for intent \'' + intent + '\' for metric \'' + metrics[i] + '\''); + intentHandlers[intent][metrics[i]].handler = handler; } } else { + console.log('Storing handler for intent \'' + intent + '\''); intentHandlers[intent].handler = handler; } }; - // This function retrieves a handler based on the intent name and slots requested. - alexa.getIntentHandler = function getIntentHandler(intentName, slots) { - if (intentName && intentHandlers[intentName]) { - if (slots) { - var slotKeys = Object.keys(slots); - for (var i = 0, len = slotKeys.length; i < len; i++) { - if (intentHandlers[intentName][slotKeys[i]] && slots[slotKeys[i]].value && - intentHandlers[intentName][slotKeys[i]][slots[slotKeys[i]].value] && - intentHandlers[intentName][slotKeys[i]][slots[slotKeys[i]].value].handler) { - - return intentHandlers[intentName][slotKeys[i]][slots[slotKeys[i]].value].handler; - } - } - } - if (intentHandlers[intentName].handler) { + // This function retrieves a handler based on the intent name and metric requested. + alexa.getIntentHandler = function getIntentHandler(intentName, metric) { + if (metric === undefined) { + console.log('Looking for handler for intent \'' + intentName + '\''); + if (intentName + && intentHandlers[intentName] + && intentHandlers[intentName].handler + ) { + console.log('Found!'); return intentHandlers[intentName].handler; } - return null; } else { - return null; + console.log('Looking for handler for intent \'' + intentName + '\' for metric \'' + metric + '\''); + if (intentName + && intentHandlers[intentName] + && intentHandlers[intentName][metric] + && intentHandlers[intentName][metric].handler + ) { + console.log('Found!'); + return intentHandlers[intentName][metric].handler + } } + console.log('Not found!'); + return null; }; alexa.addToRollup = function(rollupGroup, handler, rollupName) { @@ -63,7 +61,6 @@ function init(env, ctx) { rollup[rollupGroup] = []; } rollup[rollupGroup].push({handler: handler, name: rollupName}); - // status = _.orderBy(status, ['priority'], ['asc']) }; alexa.getRollup = function(rollupGroup, sbx, slots, locale, callback) { @@ -110,4 +107,4 @@ function init(env, ctx) { return alexa; } -module.exports = init; +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index c440f613e0a..52aec073759 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -15,7 +15,9 @@ var AR = [-0.723, 1.716]; //TODO: move this to css var AR2_COLOR = 'cyan'; +// eslint-disable-next-line no-unused-vars function init (ctx) { + var translate = ctx.language.translate; var ar2 = { name: 'ar2' @@ -23,7 +25,7 @@ function init (ctx) { , pluginType: 'forecast' }; - function buildTitle(prop, sbx) { + function buildTitle (prop, sbx) { var rangeLabel = prop.eventName ? sbx.translate(prop.eventName, { ci: true }).toUpperCase() : sbx.translate('Check BG'); var title = sbx.levels.toDisplay(prop.level) + ', ' + rangeLabel; @@ -35,7 +37,7 @@ function init (ctx) { } ar2.setProperties = function setProperties (sbx) { - sbx.offerProperty('ar2', function setAR2 ( ) { + sbx.offerProperty('ar2', function setAR2 () { var prop = { forecast: ar2.forecast(sbx) @@ -49,7 +51,7 @@ function init (ctx) { } var predicted = prop.forecast && prop.forecast.predicted; - var scaled = predicted && _.map(predicted, function(p) { return sbx.scaleEntry(p) } ); + var scaled = predicted && _.map(predicted, function(p) { return sbx.scaleEntry(p) }); if (scaled && scaled.length >= 3) { prop.displayLine = 'BG 15m: ' + scaled[2] + ' ' + sbx.unitsLabel; @@ -106,8 +108,8 @@ function init (ctx) { return result; }; - ar2.updateVisualisation = function updateVisualisation(sbx) { - sbx.pluginBase.addForecastPoints(ar2.forecastCone(sbx), {type: 'ar2', label: 'AR2 Forecast'}); + ar2.updateVisualisation = function updateVisualisation (sbx) { + sbx.pluginBase.addForecastPoints(ar2.forecastCone(sbx), { type: 'ar2', label: 'AR2 Forecast' }); }; ar2.forecastCone = function forecastCone (sbx) { @@ -118,7 +120,7 @@ function init (ctx) { var coneFactor = getConeFactor(sbx); - function pushConePoints(result, step) { + function pushConePoints (result, step) { var next = incrementAR2(result); //offset from points so they are at a unique time @@ -145,7 +147,7 @@ function init (ctx) { return result.points; }; - function alexaAr2Handler (next, slots, sbx) { + function virtAsstAr2Handler (next, slots, sbx) { if (sbx.properties.ar2.forecast.predicted) { var forecast = sbx.properties.ar2.forecast.predicted; var max = forecast[0].mgdl; @@ -162,26 +164,41 @@ function init (ctx) { maxForecastMills = forecast[i].mills; } } - var response = 'You are expected to be between ' + min + ' and ' + max + ' over the ' + moment(maxForecastMills).from(moment(sbx.time)); - next('AR2 Forecast', response); + var response = ''; + if (min === max) { + response = translate('virtAsstAR2ForecastAround', { + params: [ + max + , moment(maxForecastMills).from(moment(sbx.time)) + ] + }); + } else { + response = translate('virtAsstAR2ForecastBetween', { + params: [ + min + , max + , moment(maxForecastMills).from(moment(sbx.time)) + ] + }); + } + next(translate('virtAsstTitleAR2Forecast'), response); } else { - next('AR2 Forecast', 'AR2 plugin does not seem to be enabled'); + next(translate('virtAsstTitleAR2Forecast'), translate('virtAsstUnknown')); } } - ar2.alexa = { + ar2.virtAsst = { intentHandlers: [{ intent: 'MetricNow' - , routableSlot:'metric' - , slots:['ar2 forecast', 'forecast'] - , intentHandler: alexaAr2Handler + , metrics: ['ar2 forecast', 'forecast'] + , intentHandler: virtAsstAr2Handler }] }; return ar2; } -function checkForecast(forecast, sbx) { +function checkForecast (forecast, sbx) { var result = undefined; if (forecast && forecast.avgLoss > URGENT_THRESHOLD) { @@ -199,7 +216,7 @@ function checkForecast(forecast, sbx) { } function selectEventType (prop, sbx) { - var predicted = prop.forecast && _.map(prop.forecast.predicted, function(p) { return sbx.scaleEntry(p) } ); + var predicted = prop.forecast && _.map(prop.forecast.predicted, function(p) { return sbx.scaleEntry(p) }); var in20mins = predicted && predicted.length >= 4 ? predicted[3] : undefined; @@ -239,7 +256,7 @@ function getConeFactor (sbx) { return value; } -function okToForecast(sbx) { +function okToForecast (sbx) { var bgnow = sbx.properties.bgnow; var delta = sbx.properties.delta; @@ -273,7 +290,7 @@ function incrementAR2 (result) { }; } -function pushPoint(result) { +function pushPoint (result) { var next = incrementAR2(result); next.points.push(ar2Point( @@ -284,8 +301,7 @@ function pushPoint(result) { return next; } - -function ar2Point(next, options) { +function ar2Point (next, options) { var step = options.step || 0; var coneFactor = options.coneFactor || 0; var offset = options.offset || 0; @@ -301,16 +317,15 @@ function ar2Point(next, options) { }; } - function buildDebug (prop, sbx) { return prop.forecast && { forecast: { avgLoss: prop.forecast.avgLoss - , predicted: _.map(prop.forecast.predicted, function(p) { return sbx.scaleEntry(p) }).join(', ') + , predicted: _.map(prop.forecast.predicted, function(p) { return sbx.scaleEntry(p) }).join(', ') } }; } -function log10(val) { return Math.log(val) / Math.LN10; } +function log10 (val) { return Math.log(val) / Math.LN10; } module.exports = init; diff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js index db47e2bf279..4347a7005ec 100644 --- a/lib/plugins/basalprofile.js +++ b/lib/plugins/basalprofile.js @@ -1,6 +1,7 @@ 'use strict'; var times = require('../times'); var moment = require('moment'); +var consts = require('../constants'); function init (ctx) { @@ -61,8 +62,18 @@ function init (ctx) { var tzMessage = profile.getTimezone() ? profile.getTimezone() : 'Timezone not set in profile'; + var sensitivity = profile.getSensitivity(sbx.time); + var units = profile.getUnits(); + + if (sbx.settings.units != units) { + sensitivity *= (sbx.settings.units === 'mmol' ? (1 / consts.MMOL_TO_MGDL) : consts.MMOL_TO_MGDL); + var decimals = (sbx.settings.units === 'mmol' ? 10 : 1); + + sensitivity = Math.round(sensitivity * decimals) / decimals; + } + var info = [{label: translate('Current basal'), value: prop.display} - , {label: translate('Sensitivity'), value: profile.getSensitivity(sbx.time) + ' ' + sbx.settings.units + ' / U'} + , {label: translate('Sensitivity'), value: sensitivity + ' ' + sbx.settings.units + ' / U'} , {label: translate('Current Carb Ratio'), value: '1 U / ' + profile.getCarbRatio(sbx.time) + 'g'} , {label: translate('Basal timezone'), value: tzMessage} , {label: '------------', value: ''} @@ -102,16 +113,16 @@ function init (ctx) { function basalMessage(slots, sbx) { var basalValue = sbx.data.profile.getTempBasal(sbx.time); - var response = 'Unable to determine current basal'; + var response = translate('virtAsstUnknown'); var preamble = ''; if (basalValue.treatment) { - preamble = (slots && slots.pwd && slots.pwd.value) ? translate('alexaPreamble3person', { + preamble = (slots && slots.pwd && slots.pwd.value) ? translate('virtAsstPreamble3person', { params: [ slots.pwd.value ] - }) : translate('alexaPreamble'); + }) : translate('virtAsstPreamble'); var minutesLeft = moment(basalValue.treatment.endmills).from(moment(sbx.time)); - response = translate('alexaBasalTemp', { + response = translate('virtAsstBasalTemp', { params: [ preamble, basalValue.totalbasal, @@ -119,12 +130,12 @@ function init (ctx) { ] }); } else { - preamble = (slots && slots.pwd && slots.pwd.value) ? translate('alexaPreamble3person', { + preamble = (slots && slots.pwd && slots.pwd.value) ? translate('virtAsstPreamble3person', { params: [ slots.pwd.value ] - }) : translate('alexaPreamble'); - response = translate('alexaBasal', { + }) : translate('virtAsstPreamble'); + response = translate('virtAsstBasal', { params: [ preamble, basalValue.totalbasal @@ -134,30 +145,28 @@ function init (ctx) { return response; } - function alexaRollupCurrentBasalHandler (slots, sbx, callback) { + function virtAsstRollupCurrentBasalHandler (slots, sbx, callback) { callback(null, {results: basalMessage(slots, sbx), priority: 1}); } - function alexaCurrentBasalhandler (next, slots, sbx) { - next('Current Basal', basalMessage(slots, sbx)); + function virtAsstCurrentBasalhandler (next, slots, sbx) { + next(translate('virtAsstTitleCurrentBasal'), basalMessage(slots, sbx)); } - basal.alexa = { + basal.virtAsst = { rollupHandlers: [{ rollupGroup: 'Status' , rollupName: 'current basal' - , rollupHandler: alexaRollupCurrentBasalHandler + , rollupHandler: virtAsstRollupCurrentBasalHandler }], intentHandlers: [{ intent: 'MetricNow' - , routableSlot:'metric' - , slots:['basal', 'current basal'] - , intentHandler: alexaCurrentBasalhandler + , metrics: ['basal', 'current basal'] + , intentHandler: virtAsstCurrentBasalhandler }] }; return basal; } - module.exports = init; diff --git a/lib/plugins/bridge.js b/lib/plugins/bridge.js index dc47f13aa9b..bf3c67e88da 100644 --- a/lib/plugins/bridge.js +++ b/lib/plugins/bridge.js @@ -46,9 +46,17 @@ function options (env) { , minutes: env.extendedSettings.bridge.minutes || 1440 }; + var interval = env.extendedSettings.bridge.interval || 60000 * 2.5; // Default: 2.5 minutes + + if (interval < 1000 || interval > 300000) { + // Invalid interval range. Revert to default + console.error("Invalid interval set: [" + interval + "ms]. Defaulting to 2.5 minutes.") + interval = 60000 * 2.5 // 2.5 minutes + } + return { login: config - , interval: env.extendedSettings.bridge.interval || 60000 * 2.5 + , interval: interval , fetch: fetch_config , nightscout: { } , maxFailures: env.extendedSettings.bridge.maxFailures || 3 diff --git a/lib/plugins/careportal.js b/lib/plugins/careportal.js index b30a51532af..2d65e1c9e9c 100644 --- a/lib/plugins/careportal.js +++ b/lib/plugins/careportal.js @@ -8,6 +8,7 @@ function init() { , pluginType: 'drawer' }; + // eslint-disable-next-line no-unused-vars careportal.getEventTypes = function getEventTypes (sbx) { //TODO: use sbx and new CAREPORTAL_EVENTTYPE_GROUPS="core temps combo dad sensor site etc" @@ -15,83 +16,87 @@ function init() { return [ { val: '' , name: '' - , bg: true, insulin: true, carbs: true, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: true, carbs: true, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'BG Check' , name: 'BG Check' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Snack Bolus' , name: 'Snack Bolus' - , bg: true, insulin: true, carbs: true, prebolus: true, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: true, carbs: true, protein: true, fat: true, prebolus: true, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Meal Bolus' , name: 'Meal Bolus' - , bg: true, insulin: true, carbs: true, prebolus: true, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: true, carbs: true, protein: true, fat: true, prebolus: true, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Correction Bolus' , name: 'Correction Bolus' - , bg: true, insulin: true, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: true, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Carb Correction' , name: 'Carb Correction' - , bg: true, insulin: false, carbs: true, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: true, protein: true, fat: true, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Combo Bolus' , name: 'Combo Bolus' - , bg: true, insulin: true, carbs: true, prebolus: true, duration: true, percent: false, absolute: false, profile: false, split: true + , bg: true, insulin: true, carbs: true, protein: true, fat: true, prebolus: true, duration: true, percent: false, absolute: false, profile: false, split: true } , { val: 'Announcement' , name: 'Announcement' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Note' , name: 'Note' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false } , { val: 'Question' , name: 'Question' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Exercise' , name: 'Exercise' - , bg: false, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false + , bg: false, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false } , { val: 'Site Change' , name: 'Pump Site Change' - , bg: true, insulin: true, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: true, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Sensor Start' , name: 'CGM Sensor Start' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Sensor Change' , name: 'CGM Sensor Insert' + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + } + , { val: 'Sensor Stop' + , name: 'CGM Sensor Stop' , bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Pump Battery Change' , name: 'Pump Battery Change' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Insulin Change' , name: 'Insulin Cartridge Change' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } , { val: 'Temp Basal Start' , name: 'Temp Basal Start' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: true, percent: true, absolute: true, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: true, percent: true, absolute: true, profile: false, split: false } , { val: 'Temp Basal End' , name: 'Temp Basal End' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false } , { val: 'Profile Switch' , name: 'Profile Switch' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: true, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: true, percent: false, absolute: false, profile: true, split: false } , { val: 'D.A.D. Alert' , name: 'D.A.D. Alert' - , bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false + , bg: true, insulin: false, carbs: false, protein: false, fat: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false } ]; diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 3858e794848..c6d4c4fdf8f 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -4,7 +4,7 @@ var _ = require('lodash') , moment = require('moment') , times = require('../times'); -function init(ctx) { +function init (ctx) { var translate = ctx.language.translate; var iob = require('./iob')(ctx); @@ -16,15 +16,15 @@ function init(ctx) { cob.RECENCY_THRESHOLD = times.mins(30).msecs; - cob.setProperties = function setProperties(sbx) { - sbx.offerProperty('cob', function setCOB ( ) { + cob.setProperties = function setProperties (sbx) { + sbx.offerProperty('cob', function setCOB () { return cob.cobTotal(sbx.data.treatments, sbx.data.devicestatus, sbx.data.profile, sbx.time); }); }; - cob.cobTotal = function cobTotal(treatments, devicestatus, profile, time, spec_profile) { + cob.cobTotal = function cobTotal (treatments, devicestatus, profile, time, spec_profile) { - if (!profile || !profile.hasData()) { + if (!profile || !profile.hasData()) { console.warn('For the COB plugin to function you need a treatment profile'); return {}; } @@ -41,21 +41,23 @@ function init(ctx) { } var devicestatusCOB = cob.lastCOBDeviceStatus(devicestatus, time); + var result = devicestatusCOB; - var treatmentCOB = (treatments !== undefined && treatments.length) ? cob.fromTreatments(treatments, devicestatus, profile, time, spec_profile) : {}; + const TEN_MINUTES = 10 * 60 * 1000; - var result = devicestatusCOB; - if (_.isEmpty(result)) { - result = treatmentCOB; + if (_.isEmpty(result) || _.isNil(result.cob) || (Date.now() - result.mills) > TEN_MINUTES) { + + var treatmentCOB = (treatments !== undefined && treatments.length) ? cob.fromTreatments(treatments, devicestatus, profile, time, spec_profile) : {}; + + result = _.cloneDeep(treatmentCOB); result.source = 'Care Portal'; - } else if (treatmentCOB) { - result.treatmentCOB = treatmentCOB; + result.treatmentCOB = _.cloneDeep(treatmentCOB); } return addDisplay(result); }; - function addDisplay(cob) { + function addDisplay (cob) { if (_.isEmpty(cob) || cob.cob === undefined) { return {}; } @@ -82,7 +84,7 @@ function init(ctx) { var recentMills = time - cob.RECENCY_THRESHOLD; return _.chain(devicestatus) - .filter(function (cobStatus) { + .filter(function(cobStatus) { return cobStatus.mills <= futureMills && cobStatus.mills >= recentMills; }) .map(cob.fromDeviceStatus) @@ -95,7 +97,7 @@ function init(ctx) { cob.COBDeviceStatusesInTimeRange = function COBDeviceStatusesInTimeRange (devicestatus, from, to) { return _.chain(devicestatus) - .filter(function (cobStatus) { + .filter(function(cobStatus) { return cobStatus.mills > from && cobStatus.mills < to; }) .map(cob.fromDeviceStatus) @@ -104,7 +106,7 @@ function init(ctx) { .value(); }; - cob.fromDeviceStatus = function fromDeviceStatus(devicestatusEntry) { + cob.fromDeviceStatus = function fromDeviceStatus (devicestatusEntry) { var cobObj; if (_.get(devicestatusEntry, 'openaps') !== undefined) { @@ -140,7 +142,7 @@ function init(ctx) { cob: lastCOB , source: 'OpenAPS' , device: devicestatusEntry.device - , mills: lastMoment.valueOf( ) + , mills: lastMoment.valueOf() }; } else if (_.get(devicestatusEntry, 'loop.cob') !== undefined) { cobObj = devicestatusEntry.loop.cob; @@ -148,7 +150,7 @@ function init(ctx) { cob: cobObj.cob , source: 'Loop' , device: devicestatusEntry.device - , mills: moment(cobObj.timestamp).valueOf( ) + , mills: moment(cobObj.timestamp).valueOf() }; } else { return {}; @@ -164,7 +166,7 @@ function init(ctx) { var isDecaying = 0; var lastDecayedBy = 0; - _.each(treatments, function eachTreatment(treatment) { + _.each(treatments, function eachTreatment (treatment) { if (treatment.carbs && treatment.mills < time) { lastCarbs = treatment; var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time, spec_profile); @@ -175,7 +177,7 @@ function init(ctx) { var actEnd = iob.calcTotal(treatments, devicestatus, profile, cCalc.decayedBy, spec_profile).activity; var avgActivity = (actStart + actEnd) / 2; // units: g = BG * scalar / BG / U * g / U - var delayedCarbs = ( avgActivity * liverSensRatio / profile.getSensitivity(treatment.mills, spec_profile) ) * profile.getCarbRatio(treatment.mills, spec_profile); + var delayedCarbs = (avgActivity * liverSensRatio / profile.getSensitivity(treatment.mills, spec_profile)) * profile.getCarbRatio(treatment.mills, spec_profile); var delayMinutes = Math.round(delayedCarbs / profile.getCarbAbsorptionRate(treatment.mills, spec_profile) * 60); if (delayMinutes > 0) { cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); @@ -211,7 +213,7 @@ function init(ctx) { }; }; - cob.carbImpact = function carbImpact(rawCarbImpact, insulinImpact) { + cob.carbImpact = function carbImpact (rawCarbImpact, insulinImpact) { var liverSensRatio = 1.0; var liverCarbImpactMax = 0.7; var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio * insulinImpact); @@ -219,12 +221,12 @@ function init(ctx) { var netCarbImpact = Math.max(0, rawCarbImpact - liverCarbImpact); var totalImpact = netCarbImpact - insulinImpact; return { - netCarbImpact: netCarbImpact, - totalImpact: totalImpact + netCarbImpact: netCarbImpact + , totalImpact: totalImpact }; }; - cob.cobCalc = function cobCalc(treatment, profile, lastDecayedBy, time, spec_profile) { + cob.cobCalc = function cobCalc (treatment, profile, lastDecayedBy, time, spec_profile) { var delay = 20; var isDecaying = 0; @@ -232,7 +234,7 @@ function init(ctx) { if (treatment.carbs) { var carbTime = new Date(treatment.mills); - + var carbs_hr = profile.getCarbAbsorptionRate(treatment.mills, spec_profile); var carbs_min = carbs_hr / 60; @@ -241,31 +243,28 @@ function init(ctx) { decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay, minutesleft) + treatment.carbs / carbs_min); if (delay > minutesleft) { initialCarbs = parseInt(treatment.carbs); - } - else { + } else { initialCarbs = parseInt(treatment.carbs) + minutesleft * carbs_min; } var startDecay = new Date(carbTime); startDecay.setMinutes(carbTime.getMinutes() + delay); if (time < lastDecayedBy || time > startDecay) { isDecaying = 1; - } - else { + } else { isDecaying = 0; } return { - initialCarbs: initialCarbs, - decayedBy: decayedBy, - isDecaying: isDecaying, - carbTime: carbTime + initialCarbs: initialCarbs + , decayedBy: decayedBy + , isDecaying: isDecaying + , carbTime: carbTime }; - } - else { + } else { return ''; } }; - cob.updateVisualisation = function updateVisualisation(sbx) { + cob.updateVisualisation = function updateVisualisation (sbx) { var prop = sbx.properties.cob; @@ -273,16 +272,16 @@ function init(ctx) { var displayCob = Math.round(prop.cob * 10) / 10; - var info = [ ]; + var info = []; if (prop.treatmentCOB !== undefined && prop.treatmentCOB.cob) { - info.push({label: translate('Careportal COB'), value: Math.round(prop.treatmentCOB.cob * 10) / 10}); + info.push({ label: translate('Careportal COB'), value: Math.round(prop.treatmentCOB.cob * 10) / 10 }); } var lastCarbs = prop.lastCarbs || (prop.treatmentCOB && prop.treatmentCOB.lastCarbs); if (lastCarbs) { var when = new Date(lastCarbs.mills).toLocaleString(); var amount = lastCarbs.carbs + 'g'; - info.push({label: translate('Last Carbs'), value: amount + ' @ ' + when}); + info.push({ label: translate('Last Carbs'), value: amount + ' @ ' + when }); } sbx.pluginBase.updatePillText(sbx, { @@ -292,22 +291,31 @@ function init(ctx) { }); }; - function alexaCOBHandler (next, slots, sbx) { - var preamble = (slots && slots.pwd && slots.pwd.value) ? slots.pwd.value.replace('\'s', '') + ' has' : 'You have'; - var value = 'no'; - if (sbx.properties.cob && sbx.properties.cob.cob !== 0) { - value = sbx.properties.cob.cob; + function virtAsstCOBHandler (next, slots, sbx) { + var response = ''; + var value = (sbx.properties.cob && sbx.properties.cob.cob) ? sbx.properties.cob.cob : 0; + if (slots && slots.pwd && slots.pwd.value) { + response = translate('virtAsstCob3person', { + params: [ + slots.pwd.value.replace('\'s', '') + , value + ] + }); + } else { + response = translate('virtAsstCob', { + params: [ + value + ] + }); } - var response = preamble + ' ' + value + ' carbohydrates on board'; - next('Current COB', response); + next(translate('virtAsstTitleCurrentCOB'), response); } - cob.alexa = { + cob.virtAsst = { intentHandlers: [{ intent: 'MetricNow' - , routableSlot:'metric' - , slots:['cob', 'carbs on board', 'carbohydrates on board'] - , intentHandler: alexaCOBHandler + , metrics: ['cob', 'carbs on board', 'carbohydrates on board'] + , intentHandler: virtAsstCOBHandler }] }; diff --git a/lib/plugins/direction.js b/lib/plugins/direction.js index 53dda0e5879..6274fc9e537 100644 --- a/lib/plugins/direction.js +++ b/lib/plugins/direction.js @@ -10,7 +10,7 @@ function init() { direction.setProperties = function setProperties (sbx) { sbx.offerProperty('direction', function setDirection ( ) { - if (sbx.data.inRetroMode && !sbx.isCurrent(sbx.lastSGVEntry())) { + if (!sbx.isCurrent(sbx.lastSGVEntry())) { return undefined; } else { return direction.info(sbx.lastSGVEntry()); @@ -52,6 +52,7 @@ function init() { var dir2Char = { NONE: '⇼' + , TripleUp: '⤊' , DoubleUp: '⇈' , SingleUp: '↑' , FortyFiveUp: '↗' @@ -59,6 +60,7 @@ function init() { , FortyFiveDown: '↘' , SingleDown: '↓' , DoubleDown: '⇊' + , TripleDown: '⤋' , 'NOT COMPUTABLE': '-' , 'RATE OUT OF RANGE': '⇕' }; @@ -75,4 +77,4 @@ function init() { } -module.exports = init; \ No newline at end of file +module.exports = init; diff --git a/lib/plugins/googlehome.js b/lib/plugins/googlehome.js new file mode 100644 index 00000000000..8e8181512c8 --- /dev/null +++ b/lib/plugins/googlehome.js @@ -0,0 +1,97 @@ +var _ = require('lodash'); +var async = require('async'); + +function init (env, ctx) { + console.log('Configuring Google Home...'); + function googleHome() { + return googleHome; + } + var intentHandlers = {}; + var rollup = {}; + + // There is no protection for a previously handled metric - one plugin can overwrite the handler of another plugin. + googleHome.configureIntentHandler = function configureIntentHandler(intent, handler, metrics) { + if (!intentHandlers[intent]) { + intentHandlers[intent] = {}; + } + if (metrics) { + for (var i = 0, len = metrics.length; i < len; i++) { + if (!intentHandlers[intent][metrics[i]]) { + intentHandlers[intent][metrics[i]] = {}; + } + console.log('Storing handler for intent \'' + intent + '\' for metric \'' + metrics[i] + '\''); + intentHandlers[intent][metrics[i]].handler = handler; + } + } else { + console.log('Storing handler for intent \'' + intent + '\''); + intentHandlers[intent].handler = handler; + } + }; + + // This function retrieves a handler based on the intent name and metric requested. + googleHome.getIntentHandler = function getIntentHandler(intentName, metric) { + console.log('Looking for handler for intent \'' + intentName + '\' for metric \'' + metric + '\''); + if (intentName && intentHandlers[intentName]) { + if (intentHandlers[intentName][metric] && intentHandlers[intentName][metric].handler) { + console.log('Found!'); + return intentHandlers[intentName][metric].handler + } else if (intentHandlers[intentName].handler) { + console.log('Found!'); + return intentHandlers[intentName].handler; + } + console.log('Not found!'); + return null; + } else { + console.log('Not found!'); + return null; + } + }; + + googleHome.addToRollup = function(rollupGroup, handler, rollupName) { + if (!rollup[rollupGroup]) { + console.log('Creating the rollup group: ', rollupGroup); + rollup[rollupGroup] = []; + } + rollup[rollupGroup].push({handler: handler, name: rollupName}); + }; + + googleHome.getRollup = function(rollupGroup, sbx, slots, locale, callback) { + var handlers = _.map(rollup[rollupGroup], 'handler'); + console.log('Rollup array for ', rollupGroup); + console.log(rollup[rollupGroup]); + var nHandlers = []; + _.each(handlers, function (handler) { + nHandlers.push(handler.bind(null, slots, sbx)); + }); + async.parallelLimit(nHandlers, 10, function(err, results) { + if (err) { + console.error('Error: ', err); + } + callback(_.map(_.orderBy(results, ['priority'], ['asc']), 'results').join(' ')); + }); + }; + + // This creates the expected Google Home response + googleHome.buildSpeechletResponse = function buildSpeechletResponse(output, expectUserResponse) { + return { + payload: { + google: { + expectUserResponse: expectUserResponse, + richResponse: { + items: [ + { + simpleResponse: { + textToSpeech: output + } + } + ] + } + } + } + }; + }; + + return googleHome; +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 363c95fb356..2568b634afd 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -12,9 +12,11 @@ function init (ctx) { var allPlugins = [] , enabledPlugins = []; - function plugins(name) { + function plugins (name) { if (name) { - return _find(allPlugins, {name: name}); + return _find(allPlugins, { + name: name + }); } else { return plugins; } @@ -35,16 +37,17 @@ function init (ctx) { , require('./careportal')(ctx) , require('./pump')(ctx) , require('./openaps')(ctx) - , require('./xdrip-js')(ctx) + , require('./xdripjs')(ctx) , require('./loop')(ctx) + , require('./override')(ctx) , require('./boluswizardpreview')(ctx) , require('./cannulaage')(ctx) , require('./sensorage')(ctx) , require('./insulinage')(ctx) , require('./batteryage')(ctx) , require('./basalprofile')(ctx) - , require('./boluscalc')(ctx) // fake plugin to show/hide - , require('./profile')(ctx) // fake plugin to hold extended settings + , require('./boluscalc')(ctx) // fake plugin to show/hide + , require('./profile')(ctx) // fake plugin to hold extended settings , require('./speech')(ctx) ]; @@ -60,7 +63,7 @@ function init (ctx) { , require('./cob')(ctx) , require('./pump')(ctx) , require('./openaps')(ctx) - , require('./xdrip-js')(ctx) + , require('./xdripjs')(ctx) , require('./loop')(ctx) , require('./boluswizardpreview')(ctx) , require('./cannulaage')(ctx) @@ -72,56 +75,52 @@ function init (ctx) { , require('./basalprofile')(ctx) ]; - plugins.registerServerDefaults = function registerServerDefaults() { + plugins.registerServerDefaults = function registerServerDefaults () { plugins.register(serverDefaultPlugins); return plugins; }; - plugins.registerClientDefaults = function registerClientDefaults() { + plugins.registerClientDefaults = function registerClientDefaults () { plugins.register(clientDefaultPlugins); return plugins; }; - plugins.register = function register(all) { - _each(all, function eachPlugin(plugin) { + plugins.register = function register (all) { + _each(all, function eachPlugin (plugin) { allPlugins.push(plugin); }); enabledPlugins = []; var enable = _get(ctx, 'settings.enable'); - function isEnabled(plugin) { + + function isEnabled (plugin) { //TODO: unify client/server env/app return enable && enable.indexOf(plugin.name) > -1; } - _each(allPlugins, function eachPlugin(plugin) { + _each(allPlugins, function eachPlugin (plugin) { plugin.enabled = isEnabled(plugin); if (plugin.enabled) { enabledPlugins.push(plugin); } }); - - console.log("Plugins registered", enabledPlugins); - }; - - plugins.isPluginEnabled = function isPluginEnabled(pluginName) { - var p = _.find(enabledPlugins, 'name', pluginName); + + plugins.isPluginEnabled = function isPluginEnabled (pluginName) { + var p = _find(enabledPlugins, 'name', pluginName); return (p !== null); } - - plugins.isPluginVisualization - plugins.getPlugin = function getPlugin(pluginName) { - return _.find(enabledPlugins, 'name', pluginName); + plugins.getPlugin = function getPlugin (pluginName) { + return _find(enabledPlugins, 'name', pluginName); } - plugins.eachPlugin = function eachPlugin(f) { + plugins.eachPlugin = function eachPlugin (f) { _each(allPlugins, f); }; - plugins.eachEnabledPlugin = function eachEnabledPlugin(f) { + plugins.eachEnabledPlugin = function eachEnabledPlugin (f) { _each(enabledPlugins, f); }; @@ -129,57 +128,72 @@ function init (ctx) { plugins.specialPlugins = 'ar2 bgnow delta direction timeago upbat rawbg errorcodes profile'; plugins.shownPlugins = function(sbx) { - return _filter(enabledPlugins, function filterPlugins(plugin) { + return _filter(enabledPlugins, function filterPlugins (plugin) { return plugins.specialPlugins.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1); }); }; - plugins.eachShownPlugins = function eachShownPlugins(sbx, f) { + plugins.eachShownPlugins = function eachShownPlugins (sbx, f) { _each(plugins.shownPlugins(sbx), f); }; - plugins.hasShownType = function hasShownType(pluginType, sbx) { - return _find(plugins.shownPlugins(sbx), function findWithType(plugin) { + plugins.hasShownType = function hasShownType (pluginType, sbx) { + return _find(plugins.shownPlugins(sbx), function findWithType (plugin) { return plugin.pluginType === pluginType; }) !== undefined; }; - plugins.setProperties = function setProperties(sbx) { + plugins.setProperties = function setProperties (sbx) { plugins.eachEnabledPlugin(function eachPlugin (plugin) { if (plugin.setProperties) { - plugin.setProperties(sbx.withExtendedSettings(plugin)); + try { + plugin.setProperties(sbx.withExtendedSettings(plugin)); + } catch (error) { + console.error('Plugin error on setProperties(): ', plugin.name, error); + } } }); }; - plugins.checkNotifications = function checkNotifications(sbx) { + plugins.checkNotifications = function checkNotifications (sbx) { plugins.eachEnabledPlugin(function eachPlugin (plugin) { if (plugin.checkNotifications) { - plugin.checkNotifications(sbx.withExtendedSettings(plugin)); + try { + plugin.checkNotifications(sbx.withExtendedSettings(plugin)); + } catch (error) { + console.error('Plugin error on checkNotifications(): ', plugin.name, error); + } } }); }; - - plugins.visualizeAlarm = function visualizeAlarm(sbx, alarm, alarmMessage) { - console.log("visualizeAlarms"); - plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { + + plugins.visualizeAlarm = function visualizeAlarm (sbx, alarm, alarmMessage) { + plugins.eachShownPlugins(sbx, function eachPlugin (plugin) { if (plugin.visualizeAlarm) { - plugin.visualizeAlarm(sbx.withExtendedSettings(plugin), alarm, alarmMessage); + try { + plugin.visualizeAlarm(sbx.withExtendedSettings(plugin), alarm, alarmMessage); + } catch (error) { + console.error('Plugin error on visualizeAlarm(): ', plugin.name, error); + } } - }); + }); }; - plugins.updateVisualisations = function updateVisualisations(sbx) { - plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { + plugins.updateVisualisations = function updateVisualisations (sbx) { + plugins.eachShownPlugins(sbx, function eachPlugin (plugin) { if (plugin.updateVisualisation) { - plugin.updateVisualisation(sbx.withExtendedSettings(plugin)); + try { + plugin.updateVisualisation(sbx.withExtendedSettings(plugin)); + } catch (error) { + console.error('Plugin error on visualizeAlarm(): ', plugin.name, error); + } } }); }; - plugins.getAllEventTypes = function getAllEventTypes(sbx) { + plugins.getAllEventTypes = function getAllEventTypes (sbx) { var all = []; - plugins.eachEnabledPlugin(function eachPlugin(plugin) { + plugins.eachEnabledPlugin(function eachPlugin (plugin) { if (plugin.getEventTypes) { var eventTypes = plugin.getEventTypes(sbx.withExtendedSettings(plugin)); if (_isArray(eventTypes)) { @@ -191,8 +205,8 @@ function init (ctx) { return all; }; - plugins.enabledPluginNames = function enabledPluginNames() { - return _map(enabledPlugins, function mapped(plugin) { + plugins.enabledPluginNames = function enabledPluginNames () { + return _map(enabledPlugins, function mapped (plugin) { return plugin.name; }).join(' '); }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 5468ff0700d..96bea03b3ff 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -34,9 +34,9 @@ function init(ctx) { if (_.isEmpty(result)) { result = treatmentResult; } else if (treatmentResult.iob) { - result.treatmentIob = treatmentResult.iob; + result.treatmentIob = +(Math.round(treatmentResult.iob + "e+3") + "e-3"); } - + if (result.iob) result.iob = +(Math.round(result.iob + "e+3") + "e-3"); return addDisplay(result); }; @@ -162,7 +162,7 @@ function init(ctx) { }); return { - iob: totalIOB + iob: +(Math.round(totalIOB + "e+3") + "e-3") , activity: totalActivity , lastBolus: lastBolus , source: translate('Care Portal') @@ -243,21 +243,19 @@ function init(ctx) { }; - function alexaIOBIntentHandler (callback, slots, sbx) { + function virtAsstIOBIntentHandler (callback, slots, sbx) { - var message = translate('alexaIobIntent', { + var message = translate('virtAsstIobIntent', { params: [ - //preamble, getIob(sbx) ] }); - //preamble + + ' insulin on board'; - callback('Current IOB', message); + callback(translate('virtAsstTitleCurrentIOB'), message); } - function alexaIOBRollupHandler (slots, sbx, callback) { + function virtAsstIOBRollupHandler (slots, sbx, callback) { var iob = getIob(sbx); - var message = translate('alexaIob', { + var message = translate('virtAsstIob', { params: [iob] }); callback(null, {results: message, priority: 2}); @@ -265,26 +263,25 @@ function init(ctx) { function getIob(sbx) { if (sbx.properties.iob && sbx.properties.iob.iob !== 0) { - return translate('alexaIobUnits', { + return translate('virtAsstIobUnits', { params: [ utils.toFixed(sbx.properties.iob.iob) ] }); } - return translate('alexaNoInsulin'); + return translate('virtAsstNoInsulin'); } - iob.alexa = { + iob.virtAsst = { rollupHandlers: [{ rollupGroup: 'Status' , rollupName: 'current iob' - , rollupHandler: alexaIOBRollupHandler + , rollupHandler: virtAsstIOBRollupHandler }] , intentHandlers: [{ intent: 'MetricNow' - , routableSlot: 'metric' - , slots: ['iob', 'insulin on board'] - , intentHandler: alexaIOBIntentHandler + , metrics: ['iob', 'insulin on board'] + , intentHandler: virtAsstIOBIntentHandler }] }; diff --git a/lib/plugins/loop.js b/lib/plugins/loop.js index 6082b6bdc00..9b099dd188c 100644 --- a/lib/plugins/loop.js +++ b/lib/plugins/loop.js @@ -7,8 +7,9 @@ var levels = require('../levels'); // var ALL_STATUS_FIELDS = ['status-symbol', 'status-label', 'iob', 'freq', 'rssi']; Unused variable -function init(ctx) { +function init (ctx) { var utils = require('../utils')(ctx); + var translate = ctx.language.translate; var loop = { name: 'loop' @@ -18,7 +19,7 @@ function init(ctx) { var firstPrefs = true; - loop.getPrefs = function getPrefs(sbx) { + loop.getPrefs = function getPrefs (sbx) { var prefs = { warn: sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 30 @@ -35,7 +36,7 @@ function init(ctx) { }; loop.setProperties = function setProperties (sbx) { - sbx.offerProperty('loop', function setLoop ( ) { + sbx.offerProperty('loop', function setLoop () { return loop.analyzeData(sbx); }); }; @@ -45,14 +46,14 @@ function init(ctx) { var recentMills = sbx.time - times.hours(recentHours).msecs; var recentData = _.chain(sbx.data.devicestatus) - .filter(function (status) { + .filter(function(status) { return ('loop' in status) && sbx.entryMills(status) <= sbx.time && sbx.entryMills(status) >= recentMills; - }).value( ); + }).value(); var prefs = loop.getPrefs(sbx); var recent = moment(sbx.time).subtract(prefs.warn / 2, 'minutes'); - function getDisplayForStatus (status) { + function getDisplayForStatus (status) { var desc = { symbol: '⚠' @@ -113,6 +114,16 @@ function init(ctx) { } } + function assignLastOverride (status) { + var override = status.override; + if (override && override.timestamp) { + override.moment = moment(override.timestamp); + if (!result.lastOverride || override.moment.isAfter(result.lastOverride.moment)) { + result.lastOverride = override; + } + } + } + function assignLastOkMoment (loopStatus) { if (!loopStatus.failureReason && (!result.lastOkMoment || loopStatus.moment.isAfter(result.lastOkMoment))) { result.lastOkMoment = loopStatus.moment; @@ -126,6 +137,7 @@ function init(ctx) { assignLastEnacted(loopStatus); assignLastLoop(loopStatus); assignLastPredicted(loopStatus); + assignLastOverride(status); assignLastOkMoment(loopStatus); } }); @@ -135,7 +147,7 @@ function init(ctx) { return result; }; - loop.checkNotifications = function checkNotifications(sbx) { + loop.checkNotifications = function checkNotifications (sbx) { var prefs = loop.getPrefs(sbx); if (!prefs.enableAlerts) { return; } @@ -162,6 +174,81 @@ function init(ctx) { } }; + loop.getEventTypes = function getEventTypes (sbx) { + + var units = sbx.settings.units; + console.log('units', units); + + var reasonconf = []; + + if (sbx.data === undefined || sbx.data.profile === undefined || sbx.data.profile.data.length == 0) { + return []; + } + + let profile = sbx.data.profile.data[0]; + + if (profile.loopSettings === undefined || profile.loopSettings.overridePresets == undefined) { + return []; + } + + let presets = profile.loopSettings.overridePresets; + + for (var i = 0; i < presets.length; i++) { + let preset = presets[i] + reasonconf.push({ name: preset.name, displayName: preset.symbol + " " + preset.name, duration: preset.duration / 60}); + } + + var postLoopNotification = function (client, data, callback) { + + $.ajax({ + method: "POST" + , headers: client.headers() + , url: '/api/v2/notifications/loop' + , data: data + }) + .done(function () { + callback(); + }) + .fail(function (jqXHR) { + callback(jqXHR.responseText); + }); + } + + return [ + { + val: 'Temporary Override' + , name: 'Temporary Override' + , bg: false + , insulin: false + , carbs: false + , prebolus: false + , duration: true + , percent: false + , absolute: false + , profile: false + , split: false + , targets: false + , reasons: reasonconf + , submitHook: postLoopNotification + }, + { + val: 'Temporary Override Cancel' + , name: 'Temporary Override Cancel' + , bg: false + , insulin: false + , carbs: false + , prebolus: false + , duration: false + , percent: false + , absolute: false + , profile: false + , split: false + , targets: false + , submitHook: postLoopNotification + } + ]; + }; + loop.updateVisualisation = function updateVisualisation (sbx) { var prop = sbx.properties.loop; @@ -171,9 +258,9 @@ function init(ctx) { return (value != null) ? prefix + value : ''; } - var events = [ ]; + var events = []; - function addRecommendedTempBasal() { + function addRecommendedTempBasal () { if (prop.lastLoop && prop.lastLoop.recommendedTempBasal) { var recommendedTempBasal = prop.lastLoop.recommendedTempBasal; @@ -185,7 +272,7 @@ function init(ctx) { valueParts = concatIOB(valueParts); valueParts = concatCOB(valueParts); - valueParts = concatEventualBG (valueParts); + valueParts = concatEventualBG(valueParts); valueParts = concatRecommendedBolus(valueParts); events.push({ @@ -195,48 +282,48 @@ function init(ctx) { } } - function addRSSI() { - + function addRSSI () { + var mostRecent = ""; var pumpRSSI = ""; var bleRSSI = ""; var reportRSSI = ""; - + _.forEach(sbx.data.devicestatus, function(entry) { - + if (entry.radioAdapter) { var entryMoment = moment(entry.created_at); - - if (mostRecent == "") { - mostRecent = entryMoment; - if (entry.radioAdapter.pumpRSSI) { - pumpRSSI = entry.radioAdapter.pumpRSSI; - } - if (entry.radioAdapter.RSSI) { - bleRSSI = entry.radioAdapter.RSSI; - } - } - - if (mostRecent < entryMoment) { - mostRecent = entryMoment; - if (entry.radioAdapter.pumpRSSI) { - pumpRSSI = entry.radioAdapter.pumpRSSI; - } - if (entry.radioAdapter.RSSI) { - bleRSSI = entry.radioAdapter.RSSI; - } - } - } + + if (mostRecent == "") { + mostRecent = entryMoment; + if (entry.radioAdapter.pumpRSSI) { + pumpRSSI = entry.radioAdapter.pumpRSSI; + } + if (entry.radioAdapter.RSSI) { + bleRSSI = entry.radioAdapter.RSSI; + } + } + + if (mostRecent < entryMoment) { + mostRecent = entryMoment; + if (entry.radioAdapter.pumpRSSI) { + pumpRSSI = entry.radioAdapter.pumpRSSI; + } + if (entry.radioAdapter.RSSI) { + bleRSSI = entry.radioAdapter.RSSI; + } + } + } }); - + if (bleRSSI != "") { - reportRSSI = "BLE RSSI: " + bleRSSI + " "; + reportRSSI = "BLE RSSI: " + bleRSSI + " "; } - + if (pumpRSSI != "") { - reportRSSI = reportRSSI + "Pump RSSI: " + pumpRSSI; + reportRSSI = reportRSSI + "Pump RSSI: " + pumpRSSI; } - + if (reportRSSI != "") { events.push({ time: mostRecent @@ -245,21 +332,21 @@ function init(ctx) { } } - - function addLastEnacted() { + + function addLastEnacted () { if (prop.lastEnacted) { var canceled = prop.lastEnacted.rate === 0 && prop.lastEnacted.duration === 0; var valueParts = [ - , 'Temp Basal' + (canceled ? ' Canceled' : ' Started') + '' + 'Temp Basal' + (canceled ? ' Canceled' : ' Started') + '' , canceled ? '' : ' ' + prop.lastEnacted.rate.toFixed(2) + 'U/hour for ' + prop.lastEnacted.duration + 'm' , valueString(', ', prop.lastEnacted.reason) ]; valueParts = concatIOB(valueParts); valueParts = concatCOB(valueParts); - valueParts = concatEventualBG (valueParts); - valueParts = concatRecommendedBolus(valueParts); + valueParts = concatEventualBG(valueParts); + valueParts = concatRecommendedBolus(valueParts); events.push({ time: prop.lastEnacted.moment @@ -273,7 +360,9 @@ function init(ctx) { var iob = prop.lastLoop.iob; valueParts = valueParts.concat([ ', IOB: ' + , sbx.roundInsulinForDisplayFormat(iob.iob) + 'U' + , iob.basaliob ? ', Basal IOB ' + sbx.roundInsulinForDisplayFormat(iob.basaliob) + 'U' : '' ]); } @@ -283,7 +372,6 @@ function init(ctx) { function concatCOB (valueParts) { if (prop.lastLoop && prop.lastLoop.cob) { - var cob = prop.lastLoop.cob; var cob = prop.lastLoop.cob.cob; cob = Math.round(cob); valueParts = valueParts.concat([ @@ -298,28 +386,29 @@ function init(ctx) { function concatEventualBG (valueParts) { if (prop.lastLoop && prop.lastLoop.predicted) { var predictedBGvalues = prop.lastLoop.predicted.values; - var eventualBG = predictedBGvalues[predictedBGvalues.length-1]; - var maxBG = Math.max.apply(null,predictedBGvalues); - var minBG = Math.min.apply(null,predictedBGvalues); - var eventualBGscaled = sbx.settings.units === 'mmol' ? - sbx.roundBGToDisplayFormat(sbx.scaleMgdl(eventualBG)) : eventualBG; - var maxBGscaled = sbx.settings.units === 'mmol' ? - sbx.roundBGToDisplayFormat(sbx.scaleMgdl(maxBG)) : maxBG; - var minBGscaled = sbx.settings.units === 'mmol' ? - sbx.roundBGToDisplayFormat(sbx.scaleMgdl(minBG)) : minBG; + var eventualBG = predictedBGvalues[predictedBGvalues.length - 1]; + var maxBG = Math.max.apply(null, predictedBGvalues); + var minBG = Math.min.apply(null, predictedBGvalues); + var eventualBGscaled = sbx.settings.units === 'mmol' ? + sbx.roundBGToDisplayFormat(sbx.scaleMgdl(eventualBG)) : eventualBG; + var maxBGscaled = sbx.settings.units === 'mmol' ? + sbx.roundBGToDisplayFormat(sbx.scaleMgdl(maxBG)) : maxBG; + var minBGscaled = sbx.settings.units === 'mmol' ? + sbx.roundBGToDisplayFormat(sbx.scaleMgdl(minBG)) : minBG; + valueParts = valueParts.concat([ ', Predicted Min-Max BG: ' - , minBGscaled - , '-' - , maxBGscaled - ,', Eventual BG: ' - , eventualBGscaled + , minBGscaled + , '-' + , maxBGscaled + , ', Eventual BG: ' + , eventualBGscaled ]); } - + return valueParts; - } - + } + function concatRecommendedBolus (valueParts) { if (prop.lastLoop && prop.lastLoop.recommendedBolus) { var recommendedBolus = prop.lastLoop.recommendedBolus; @@ -330,14 +419,13 @@ function init(ctx) { } return valueParts; - } - - - function getForecastPoints ( ) { - var points = [ ]; + } + + function getForecastPoints () { + var points = []; function toPoints (startTime, offset) { - return function toPoint (value, index) { + return function toPoint (value, index) { return { mgdl: value , color: '#ff00ff' @@ -353,12 +441,6 @@ function init(ctx) { if (predicted.values) { points = points.concat(_.map(predicted.values, toPoints(startTime, 0))); } - // if (prop.lastPredBGs.IOB) { - // points = points.concat(_.map(prop.lastPredBGs.IOB, toPoints(moment, 3000))); - // } - // if (prop.lastPredBGs.COB) { - // points = points.concat(_.map(prop.lastPredBGs.COB, toPoints(moment, 7000))); - // } } return points; @@ -375,12 +457,12 @@ function init(ctx) { } else if ('looping' === prop.display.code) { addLastEnacted(); } else { - addRecommendedTempBasal(); + addRecommendedTempBasal(); } - + addRSSI(); - var sorted = _.sortBy(events, function toMill(event) { + var sorted = _.sortBy(events, function toMill (event) { return event.time.valueOf(); }).reverse(); @@ -397,12 +479,23 @@ function init(ctx) { loopName = prop.lastLoop.name; } + var eventualBGValue = ''; + if (prop.lastLoop && prop.lastLoop.predicted) { + var predictedBGvalues = prop.lastLoop.predicted.values; + var eventualBG = predictedBGvalues[predictedBGvalues.length - 1]; + if (sbx.settings.units === 'mmol') { + eventualBG = sbx.roundBGToDisplayFormat(sbx.scaleMgdl(eventualBG)); + } + eventualBGValue = ' ↝ ' + eventualBG; + } + var label = loopName + ' ' + prop.display.symbol; - var lastLoopMoment = prop.lastLoop ? prop.lastLoop.moment : null; + var lastLoopValue = prop.lastLoop ? + utils.timeFormat(prop.lastLoop.moment, sbx) + eventualBGValue : null; sbx.pluginBase.updatePillText(loop, { - value: utils.timeFormat(lastLoopMoment, sbx) + value: lastLoopValue , label: label , info: info , pillClass: statusClass(prop, prefs, sbx) @@ -410,11 +503,11 @@ function init(ctx) { var forecastPoints = getForecastPoints(); if (forecastPoints && forecastPoints.length > 0) { - sbx.pluginBase.addForecastPoints(forecastPoints, {type: 'loop', label: 'Loop Forecasts'}); + sbx.pluginBase.addForecastPoints(forecastPoints, { type: 'loop', label: 'Loop Forecasts' }); } }; - function alexaForecastHandler (next, slots, sbx) { + function virtAsstForecastHandler (next, slots, sbx) { if (sbx.properties.loop.lastLoop.predicted) { var forecast = sbx.properties.loop.lastLoop.predicted.values; var max = forecast[0]; @@ -424,7 +517,7 @@ function init(ctx) { var startPrediction = moment(sbx.properties.loop.lastLoop.predicted.startDate); var endPrediction = startPrediction.clone().add(maxForecastIndex * 5, 'minutes'); if (endPrediction.valueOf() < sbx.time) { - next('Loop Forecast', 'Unable to forecast with the data that is available'); + next(translate('virtAsstTitleLoopForecast'), translate('virtAsstForecastUnavailable')); } else { for (var i = 1, len = forecast.slice(0, maxForecastIndex).length; i < len; i++) { if (forecast[i] > max) { @@ -434,35 +527,52 @@ function init(ctx) { min = forecast[i]; } } - var value = ''; + var response = ''; if (min === max) { - value = 'around ' + max; + response = translate('virtAsstLoopForecastAround', { + params: [ + max + , moment(endPrediction).from(moment(sbx.time)) + ] + }); } else { - value = 'between ' + min + ' and ' + max; + response = translate('virtAsstLoopForecastBetween', { + params: [ + min + , max + , moment(endPrediction).from(moment(sbx.time)) + ] + }); } - var response = 'According to the loop forecast you are expected to be ' + value + ' over the next ' + moment(endPrediction).from(moment(sbx.time)); - next('Loop Forecast', response); + next(translate('virtAsstTitleLoopForecast'), response); } } else { - next('Loop forecast', 'Loop plugin does not seem to be enabled'); + next(translate('virtAsstTitleLoopForecast'), translate('virtAsstUnknown')); } } - function alexaLastLoopHandler(next, slots, sbx) { - console.log(JSON.stringify(sbx.properties.loop.lastLoop)); - var response = 'The last successful loop was ' + moment(sbx.properties.loop.lastOkMoment).from(moment(sbx.time)); - next('Last loop', response); + function virtAsstLastLoopHandler (next, slots, sbx) { + if (sbx.properties.loop.lastLoop) { + console.log(JSON.stringify(sbx.properties.loop.lastLoop)); + var response = translate('virtAsstLastLoop', { + params: [ + moment(sbx.properties.loop.lastOkMoment).from(moment(sbx.time)) + ] + }); + next(translate('virtAsstTitleLastLoop'), response); + } else { + next(translate('virtAsstTitleLastLoop'), translate('virtAsstUnknown')); + } } - loop.alexa = { + loop.virtAsst = { intentHandlers: [{ intent: 'MetricNow' - , routableSlot: 'metric' - , slots: ['loop forecast', 'forecast'] - , intentHandler: alexaForecastHandler + , metrics: ['loop forecast', 'forecast'] + , intentHandler: virtAsstForecastHandler }, { intent: 'LastLoop' - , intentHandler: alexaLastLoopHandler + , intentHandler: virtAsstLastLoopHandler }] }; @@ -501,5 +611,4 @@ function init(ctx) { } - module.exports = init; diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md deleted file mode 100644 index a7d89084376..00000000000 --- a/lib/plugins/maker-setup.md +++ /dev/null @@ -1,88 +0,0 @@ - - -**Table of Contents** - -- [Nightscout/IFTTT Maker](#nightscoutifttt-maker) - - [Overview](#overview) - - [Events](#events) - - [Configuration](#configuration) - - [Create a recipe](#create-a-recipe) - - [Start [creating a recipe](https://ifttt.com/myrecipes/personal/new)](#start-creating-a-recipehttpsiftttcommyrecipespersonalnew) - - [1. Choose a Trigger Channel](#1-choose-a-trigger-channel) - - [2. Choose a Trigger](#2-choose-a-trigger) - - [3. Complete Trigger Fields](#3-complete-trigger-fields) - - [4. That](#4-that) - - [5. Choose an Action](#5-choose-an-action) - - [6. Complete Action Fields](#6-complete-action-fields) - - [7. Create and Connect](#7-create-and-connect) - - [Result](#result) - - - -Nightscout/IFTTT Maker -====================================== - -## Overview - - In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). - - With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. - -## Events - - Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: - - * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. - * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. - * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-warning-high` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-high` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. - * `ns-warning-low` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-low` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. - * `ns-info-treatmentnotify` - When a treatment is entered into the care portal this event is triggered. It will be sent in addition to `ns-event` and `ns-info`. - * `ns-warning-bwp` - When the BWP plugin generates a warning alarm. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-bwp` - When the BWP plugin generates an urgent alarm. It will be sent in addition to `ns-event` and `ns-urget`. - -## Configuration - - 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) - 2. Find your secret key on the [maker page](https://ifttt.com/maker) - 3. Configure Nightscout by setting these environment variables: - * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. - * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` - -## Create a recipe - -### Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) -![screen shot 2015-06-29 at 10 58 48 pm](https://cloud.githubusercontent.com/assets/751143/8425240/bab51986-1eb8-11e5-88fb-5aed311896be.png) - -### 1. Choose a Trigger Channel - ![screen shot 2015-06-29 at 10 59 01 pm](https://cloud.githubusercontent.com/assets/751143/8425243/c007ace6-1eb8-11e5-96d1-b13f9c3d071f.png) - -### 2. Choose a Trigger - ![screen shot 2015-06-29 at 10 59 18 pm](https://cloud.githubusercontent.com/assets/751143/8425246/c77c5a4e-1eb8-11e5-9084-32ae40518ee0.png) - -### 3. Complete Trigger Fields - ![screen shot 2015-06-29 at 10 59 33 pm](https://cloud.githubusercontent.com/assets/751143/8425249/ced7b450-1eb8-11e5-95a3-730f6b9b2925.png) - -### 4. That - ![screen shot 2015-06-29 at 10 59 46 pm](https://cloud.githubusercontent.com/assets/751143/8425251/d46e1dc8-1eb8-11e5-91be-8dc731e308b2.png) - -### 5. Choose an Action - ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) - -### 6. Complete Action Fields - **Example:** `Nightscout: {{Value1}} {{Value2}} {{Value3}}` - - ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) - ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) - -### 7. Create and Connect - ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) - -### Result - ![cinpiqkumaa33u7](https://cloud.githubusercontent.com/assets/751143/8425925/e7d08d2c-1ebf-11e5-853c-cdc5381c4186.png) - - diff --git a/lib/plugins/openaps.js b/lib/plugins/openaps.js index 78c4ad2ca35..59af42c0e61 100644 --- a/lib/plugins/openaps.js +++ b/lib/plugins/openaps.js @@ -4,10 +4,11 @@ var _ = require('lodash'); var moment = require('moment'); var times = require('../times'); var levels = require('../levels'); +var consts = require('../constants'); // var ALL_STATUS_FIELDS = ['status-symbol', 'status-label', 'iob', 'meal-assist', 'freq', 'rssi']; Unused variable -function init(ctx) { +function init (ctx) { var utils = require('../utils')(ctx); var openaps = { name: 'openaps' @@ -17,7 +18,15 @@ function init(ctx) { var translate = ctx.language.translate; var firstPrefs = true; - openaps.getPrefs = function getPrefs(sbx) { + openaps.getClientPrefs = function getClientPrefs() { + return ([{ + label: "Color prediction lines", + id: "colorPredictionLines", + type: "boolean" + }]); + } + + openaps.getPrefs = function getPrefs (sbx) { function cleanList (value) { return decodeURIComponent(value || '').toLowerCase().split(' '); @@ -27,31 +36,41 @@ function init(ctx) { return _.isEmpty(list) || _.isEmpty(list[0]); } + const settings = sbx.extendedSettings || {}; - var fields = cleanList(sbx.extendedSettings.fields); + var fields = cleanList(settings.fields); fields = isEmpty(fields) ? ['status-symbol', 'status-label', 'iob', 'meal-assist', 'rssi'] : fields; - var retroFields = cleanList(sbx.extendedSettings.retroFields); + var retroFields = cleanList(settings.retroFields); retroFields = isEmpty(retroFields) ? ['status-symbol', 'status-label', 'iob', 'meal-assist', 'rssi'] : retroFields; + if (typeof settings.colorPredictionLines == 'undefined') { + settings.colorPredictionLines = true; + } + var prefs = { fields: fields , retroFields: retroFields - , warn: sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 30 - , urgent: sbx.extendedSettings.urgent ? sbx.extendedSettings.urgent : 60 - , enableAlerts: sbx.extendedSettings.enableAlerts + , warn: settings.warn ? settings.warn : 30 + , urgent: settings.urgent ? settings.urgent : 60 + , enableAlerts: settings.enableAlerts + , predIOBColor: settings.predIobColor ? settings.predIobColor : '#1e88e5' + , predCOBColor: settings.predCobColor ? settings.predCobColor : '#FB8C00' + , predACOBColor: settings.predAcobColor ? settings.predAcobColor : '#FB8C00' + , predZTColor: settings.predZtColor ? settings.predZtColor : '#00d2d2' + , predUAMColor: settings.predUamColor ? settings.predUamColor : '#c9bd60' + , colorPredictionLines: settings.colorPredictionLines }; if (firstPrefs) { firstPrefs = false; - console.info('OpenAPS Prefs:', prefs); } return prefs; }; openaps.setProperties = function setProperties (sbx) { - sbx.offerProperty('openaps', function setOpenAPS ( ) { + sbx.offerProperty('openaps', function setOpenAPS () { return openaps.analyzeData(sbx); }); }; @@ -61,10 +80,10 @@ function init(ctx) { var recentMills = sbx.time - times.hours(recentHours).msecs; var recentData = _.chain(sbx.data.devicestatus) - .filter(function (status) { + .filter(function(status) { return ('openaps' in status) && sbx.entryMills(status) <= sbx.time && sbx.entryMills(status) >= recentMills; }) - .map(function (status) { + .map(function(status) { if (status.openaps && _.isArray(status.openaps.iob) && status.openaps.iob.length > 0) { status.openaps.iob = status.openaps.iob[0]; if (status.openaps.iob.time) { @@ -73,7 +92,7 @@ function init(ctx) { } return status; }) - .value( ); + .value(); var prefs = openaps.getPrefs(sbx); var recent = moment(sbx.time).subtract(prefs.warn / 2, 'minutes'); @@ -88,7 +107,7 @@ function init(ctx) { , lastPredBGs: null }; - function getDevice(status) { + function getDevice (status) { var uri = status.device || 'device'; var device = result.seenDevices[uri]; @@ -105,7 +124,7 @@ function init(ctx) { function toMoments (status) { return { - when: moment(status.mills) + when: moment(status.mills) , enacted: status.openaps.enacted && status.openaps.enacted.timestamp && (status.openaps.enacted.recieved || status.openaps.enacted.received) && moment(status.openaps.enacted.timestamp) , notEnacted: status.openaps.enacted && status.openaps.enacted.timestamp && !(status.openaps.enacted.recieved || status.openaps.enacted.received) && moment(status.openaps.enacted.timestamp) , suggested: status.openaps.suggested && status.openaps.suggested.timestamp && moment(status.openaps.suggested.timestamp) @@ -113,7 +132,7 @@ function init(ctx) { }; } - function momentsToLoopStatus (moments, noWarning) { + function momentsToLoopStatus (moments, noWarning) { var status = { symbol: '⚠' @@ -122,8 +141,7 @@ function init(ctx) { }; if (moments.notEnacted && ( - (moments.enacted && moments.notEnacted.isAfter(moments.enacted)) || (!moments.enacted && moments.notEnacted.isAfter(recent))) - ) { + (moments.enacted && moments.notEnacted.isAfter(moments.enacted)) || (!moments.enacted && moments.notEnacted.isAfter(recent)))) { status.symbol = 'x'; status.code = 'notenacted'; status.label = 'Not Enacted'; @@ -160,7 +178,7 @@ function init(ctx) { enacted.moment = moment(enacted.timestamp); result.lastEnacted = enacted; if (enacted.predBGs && (!result.lastPredBGs || enacted.moment.isAfter(result.lastPredBGs.moment))) { - result.lastPredBGs = _.isArray(enacted.predBGs) ? {values: enacted.predBGs} : enacted.predBGs; + result.lastPredBGs = _.isArray(enacted.predBGs) ? { values: enacted.predBGs } : enacted.predBGs; result.lastPredBGs.moment = enacted.moment; } } @@ -175,7 +193,7 @@ function init(ctx) { suggested.moment = moment(suggested.timestamp); result.lastSuggested = suggested; if (suggested.predBGs && (!result.lastPredBGs || suggested.moment.isAfter(result.lastPredBGs.moment))) { - result.lastPredBGs = _.isArray(suggested.predBGs) ? {values: suggested.predBGs} : suggested.predBGs; + result.lastPredBGs = _.isArray(suggested.predBGs) ? { values: suggested.predBGs } : suggested.predBGs; result.lastPredBGs.moment = suggested.moment; } } @@ -203,11 +221,11 @@ function init(ctx) { result.lastEventualBG = result.lastSuggested.eventualBG; } } else if (result.lastEnacted && result.lastEnacted.moment) { - result.lastLoopMoment = result.lastEnacted.moment; - result.lastEventualBG = result.lastEnacted.eventualBG; + result.lastLoopMoment = result.lastEnacted.moment; + result.lastEventualBG = result.lastEnacted.eventualBG; } else if (result.lastSuggested && result.lastSuggested.moment) { - result.lastLoopMoment = result.lastSuggested.moment; - result.lastEventualBG = result.lastSuggested.eventualBG; + result.lastLoopMoment = result.lastSuggested.moment; + result.lastEventualBG = result.lastSuggested.eventualBG; } result.status = momentsToLoopStatus({ @@ -220,43 +238,68 @@ function init(ctx) { }; openaps.getEventTypes = function getEventTypes (sbx) { - - var units = sbx.settings.units; - console.log('units', units); - + + var units = sbx.settings.units; + console.log('units', units); + var reasonconf = []; - + if (units == 'mmol') { - reasonconf.push({ name: translate('Eating Soon'), targetTop: 4.5, targetBottom: 4.5, duration: 60 }); - reasonconf.push({ name: translate('Activity'), targetTop: 8, targetBottom: 6.5, duration: 120 }); + reasonconf.push({ name: translate('Eating Soon'), targetTop: 4.5, targetBottom: 4.5, duration: 60 }); + reasonconf.push({ name: translate('Activity'), targetTop: 8, targetBottom: 6.5, duration: 120 }); } else { - reasonconf.push({ name: translate('Eating Soon'), targetTop: 80, targetBottom: 80, duration: 60 }); - reasonconf.push({ name: translate('Activity'), targetTop: 140, targetBottom: 120, duration: 120 }); + reasonconf.push({ name: translate('Eating Soon'), targetTop: 80, targetBottom: 80, duration: 60 }); + reasonconf.push({ name: translate('Activity'), targetTop: 140, targetBottom: 120, duration: 120 }); } - - reasonconf.push({ name: 'Manual' }); - + + reasonconf.push({ name: 'Manual' }); + return [ { val: 'Temporary Target' , name: 'Temporary Target' - , bg: false, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false - , targets: true, reasons: reasonconf + , bg: false + , insulin: false + , carbs: false + , prebolus: false + , duration: true + , percent: false + , absolute: false + , profile: false + , split: false + , targets: true + , reasons: reasonconf } , { val: 'Temporary Target Cancel' , name: 'Temporary Target Cancel' - , bg: false, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false, profile: false, split: false - } + , bg: false + , insulin: false + , carbs: false + , prebolus: false + , duration: false + , percent: false + , absolute: false + , profile: false + , split: false + } , { val: 'OpenAPS Offline' , name: 'OpenAPS Offline' - , bg: false, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false + , bg: false + , insulin: false + , carbs: false + , prebolus: false + , duration: true + , percent: false + , absolute: false + , profile: false + , split: false } ]; }; - openaps.checkNotifications = function checkNotifications(sbx) { + openaps.checkNotifications = function checkNotifications (sbx) { var prefs = openaps.getPrefs(sbx); if (!prefs.enableAlerts) { return; } @@ -283,8 +326,8 @@ function init(ctx) { } }; - openaps.findOfflineMarker = function findOfflineMarker(sbx) { - return _.findLast(sbx.data.treatments, function match(treatment) { + openaps.findOfflineMarker = function findOfflineMarker (sbx) { + return _.findLast(sbx.data.treatments, function match (treatment) { var eventTime = sbx.entryMills(treatment); var eventEnd = treatment.duration ? eventTime + times.mins(treatment.duration).msecs : eventTime; return eventTime <= sbx.time && treatment.eventType === 'OpenAPS Offline' && eventEnd >= sbx.time; @@ -302,13 +345,19 @@ function init(ctx) { return value ? prefix + value : ''; } - var events = [ ]; + var events = []; - function addSuggestion() { + function addSuggestion () { if (prop.lastSuggested) { + var bg = prop.lastSuggested.bg; + var units = sbx.data.profile.getUnits(); + + if (units === 'mmol') { + bg = Math.round(bg / consts.MMOL_TO_MGDL * 10) / 10; + } var valueParts = [ - valueString('BG: ', prop.lastSuggested.bg) + valueString('BG: ', bg) , valueString(', ', prop.lastSuggested.reason) , prop.lastSuggested.sensitivityRatio ? ', Sensitivity Ratio: ' + prop.lastSuggested.sensitivityRatio : '' ]; @@ -337,14 +386,23 @@ function init(ctx) { return valueParts; } - function getForecastPoints ( ) { - var points = [ ]; + function getForecastPoints () { + var points = []; function toPoints (offset, forecastType) { - return function toPoint (value, index) { + return function toPoint (value, index) { + var colors = { + 'Values': '#ff00ff' + , 'IOB': prefs.predIOBColor + , 'Zero-Temp': prefs.predZTColor + , 'COB': prefs.predCOBColor + , 'Accel-COB': prefs.predACOBColor + , 'UAM': prefs.predUAMColor + } + return { mgdl: value - , color: '#ff00ff' + , color: prefs.colorPredictionLines ? colors[forecastType] : '#ff00ff' , mills: prop.lastPredBGs.moment.valueOf() + times.mins(5 * index).msecs + offset , noFade: true , forecastType: forecastType @@ -413,7 +471,7 @@ function init(ctx) { } if (device.mmtune) { - var best = _.maxBy(device.mmtune.scanDetails, function (d) { + var best = _.maxBy(device.mmtune.scanDetails, function(d) { return d[2]; }); @@ -430,7 +488,7 @@ function init(ctx) { }); }); - var sorted = _.sortBy(events, function toMill(event) { + var sorted = _.sortBy(events, function toMill (event) { return event.time.valueOf(); }).reverse(); @@ -455,40 +513,45 @@ function init(ctx) { var forecastPoints = getForecastPoints(); if (forecastPoints && forecastPoints.length > 0) { - sbx.pluginBase.addForecastPoints(forecastPoints, {type: 'openaps', label: 'OpenAPS Forecasts'}); + sbx.pluginBase.addForecastPoints(forecastPoints, { type: 'openaps', label: 'OpenAPS Forecasts' }); } }; - function alexaForecastHandler (next, slots, sbx) { + function virtAsstForecastHandler (next, slots, sbx) { if (sbx.properties.openaps && sbx.properties.openaps.lastEventualBG) { - var response = translate('alexaOpenAPSForecast', { + var response = translate('virtAsstOpenAPSForecast', { params: [ sbx.properties.openaps.lastEventualBG - ]} - ); - next('Loop Forecast', response); + ] + }); + next(translate('virtAsstTitleOpenAPSForecast'), response); + } else { + next(translate('virtAsstTitleOpenAPSForecast'), translate('virtAsstUnknown')); } } - function alexaLastLoopHandler (next, slots, sbx) { - console.log(JSON.stringify(sbx.properties.openaps.lastLoopMoment)); - var response = translate('alexaLastLoop', { - params: [ - moment(sbx.properties.openaps.lastLoopMoment).from(moment(sbx.time)) - ] - }); - next('Last loop', response); + function virtAsstLastLoopHandler (next, slots, sbx) { + if (sbx.properties.openaps.lastLoopMoment) { + console.log(JSON.stringify(sbx.properties.openaps.lastLoopMoment)); + var response = translate('virtAsstLastLoop', { + params: [ + moment(sbx.properties.openaps.lastLoopMoment).from(moment(sbx.time)) + ] + }); + next(translate('virtAsstTitleLastLoop'), response); + } else { + next(translate('virtAsstTitleLastLoop'), translate('virtAsstUnknown')); + } } - openaps.alexa = { + openaps.virtAsst = { intentHandlers: [{ intent: 'MetricNow' - , routableSlot: 'metric' - , slots: ['openaps forecast', 'forecast'] - , intentHandler: alexaForecastHandler + , metrics: ['openaps forecast', 'forecast'] + , intentHandler: virtAsstForecastHandler }, { intent: 'LastLoop' - , intentHandler: alexaLastLoopHandler + , intentHandler: virtAsstLastLoopHandler }] }; diff --git a/lib/plugins/override.js b/lib/plugins/override.js new file mode 100644 index 00000000000..ba57b11761f --- /dev/null +++ b/lib/plugins/override.js @@ -0,0 +1,67 @@ +'use strict'; + +function init() { + var override = { + name: 'override' + , label: 'Override' + , pluginType: 'pill-status' + }; + + override.isActive = function isActive(overrideStatus, sbx) { + + if (!overrideStatus) { + return false; + } else { + var endMoment = overrideStatus.duration ? overrideStatus.moment.clone().add(overrideStatus.duration, 'seconds') : null; + overrideStatus.endMoment = endMoment; + return overrideStatus.active && (!endMoment || endMoment.isAfter(sbx.time)); + } + + }; + + override.updateVisualisation = function updateVisualisation (sbx) { + var lastOverride = sbx.properties.loop.lastOverride; + var info = [ ]; + var label = ''; + var isActive = override.isActive(lastOverride, sbx); + + if (isActive) { + if (lastOverride.currentCorrectionRange) { + var max = lastOverride.currentCorrectionRange.maxValue; + var min = lastOverride.currentCorrectionRange.minValue; + + if (sbx.settings.units === 'mmol') { + max = sbx.roundBGToDisplayFormat(sbx.scaleMgdl(max)); + min = sbx.roundBGToDisplayFormat(sbx.scaleMgdl(min)); + } + + if (lastOverride.currentCorrectionRange.minValue === lastOverride.currentCorrectionRange.maxValue) { + label += 'BG Target: ' + min; + } else { + label += 'BG Targets: ' + min + ':' + max; + } + } + if ((lastOverride.multiplier || lastOverride.multiplier === 0) && lastOverride.multiplier !== 1) { + var multiplier = (lastOverride.multiplier * 100).toFixed(0); + label += ' | O: ' + multiplier + '%'; + } + } + + var endOverrideValue = lastOverride && lastOverride.endMoment ? + '⇥ ' + lastOverride.endMoment.format('LT') : (lastOverride ? '∞' : ''); + + sbx.pluginBase.updatePillText(override, { + value: endOverrideValue + , label: label + , info: info + , hide: !isActive + }); + + }; + + return override; + +} + + +module.exports = init; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index a6f0e77e455..0e2adad3586 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -10,7 +10,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { var pluginBase = { }; pluginBase.forecastInfos = []; - pluginBase.forecastPoints = []; + pluginBase.forecastPoints = {}; function findOrCreatePill (plugin) { var container = null; @@ -84,9 +84,9 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { }).join('
\n'); pill.mouseover(function pillMouseover (event) { - tooltip.transition().duration(200).style('opacity', .9); + tooltip.style('opacity', .9); - var windowWidth = $(tooltip).parent().parent().width(); + var windowWidth = $(tooltip.node()).parent().parent().width(); var left = event.pageX + TOOLTIP_WIDTH < windowWidth ? event.pageX : windowWidth - TOOLTIP_WIDTH - 10; tooltip.html(html) .style('left', left + 'px') @@ -94,9 +94,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { }); pill.mouseout(function pillMouseout ( ) { - tooltip.transition() - .duration(200) - .style('opacity', 0); + tooltip.style('opacity', 0); }); } else { pill.off('mouseover'); @@ -113,7 +111,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { }); pluginBase.forecastInfos.push(info); - pluginBase.forecastPoints = pluginBase.forecastPoints.concat(points); + pluginBase.forecastPoints[info.type] = points; }; return pluginBase; diff --git a/lib/plugins/pump.js b/lib/plugins/pump.js index dc495bfcc1e..7e71c21e1b1 100644 --- a/lib/plugins/pump.js +++ b/lib/plugins/pump.js @@ -135,38 +135,58 @@ function init (ctx) { }); }; - function alexaReservoirHandler (next, slots, sbx) { - var response = translate('alexaReservoir', { + function virtAsstReservoirHandler (next, slots, sbx) { + var reservoir = sbx.properties.pump.pump.reservoir; + if (reservoir || reservoir === 0) { + var response = translate('virtAsstReservoir', { params: [ - sbx.properties.pump.pump.reservoir + reservoir ] - }); - next('Remaining insulin', response); + }); + next(translate('virtAsstTitlePumpReservoir'), response); + } else { + next(translate('virtAsstTitlePumpReservoir'), translate('virtAsstUnknown')); + } } - function alexaBatteryHandler (next, slots, sbx) { + function virtAsstBatteryHandler (next, slots, sbx) { var battery = _.get(sbx, 'properties.pump.data.battery'); if (battery) { - var response = translate('alexaPumpBattery', { + var response = translate('virtAsstPumpBattery', { params: [ battery.value, battery.unit ] }); - next('Pump battery', response); + next(translate('virtAsstTitlePumpBattery'), response); } else { - next(); + next(translate('virtAsstTitlePumpBattery'), translate('virtAsstUnknown')); } } - pump.alexa = { - intentHandlers:[{ - intent: 'InsulinRemaining', - intentHandler: alexaReservoirHandler - }, { - intent: 'PumpBattery', - intentHandler: alexaBatteryHandler - }] + pump.virtAsst = { + intentHandlers:[ + { + // backwards compatibility + intent: 'InsulinRemaining', + intentHandler: virtAsstReservoirHandler + } + , { + // backwards compatibility + intent: 'PumpBattery', + intentHandler: virtAsstBatteryHandler + } + , { + intent: 'MetricNow' + , metrics: ['pump reservoir'] + , intentHandler: virtAsstReservoirHandler + } + , { + intent: 'MetricNow' + , metrics: ['pump battery'] + , intentHandler: virtAsstBatteryHandler + } + ] }; function statusClass (level) { @@ -215,6 +235,10 @@ function init (ctx) { } else { result.reservoir.level = levels.NONE; } + } else if (result.manufacturer === 'Insulet' && result.model === 'Eros') { + result.reservoir = { + label: 'Reservoir', display: '50+ U' + } } } @@ -264,7 +288,7 @@ function init (ctx) { if (pump.warnOnSuspend && pump.status.suspended) { result.status.level = levels.WARN; result.status.message = 'Pump Suspended'; - }; + } } result.status = { value: status, display: status, label: translate('Status') }; } @@ -277,6 +301,8 @@ function init (ctx) { level: levels.NONE , clock: pump.clock ? { value: moment(pump.clock) } : null , reservoir: pump.reservoir || pump.reservoir === 0 ? { value: pump.reservoir } : null + , manufacturer: pump.manufacturer + , model: pump.model , extended: pump.extended || null }; diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index a3853f88e11..3248126b046 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -13,6 +13,12 @@ function init (ctx) { , pillFlip: true }; + rawbg.getPrefs = function getPrefs (sbx) { + return { + display: (sbx && sbx.extendedSettings.display) ? sbx.extendedSettings.display : 'unsmoothed' + }; + }; + rawbg.setProperties = function setProperties (sbx) { sbx.offerProperty('rawbg', function setRawBG ( ) { var result = { }; @@ -49,14 +55,18 @@ function init (ctx) { sbx.pluginBase.updatePillText(rawbg, options); }; - rawbg.calc = function calc(sgv, cal) { + rawbg.calc = function calc(sgv, cal, sbx) { var raw = 0; var cleaned = cleanValues(sgv, cal); + var prefs = rawbg.getPrefs(sbx); + if (cleaned.slope === 0 || cleaned.unfiltered === 0 || cleaned.scale === 0) { raw = 0; - } else if (cleaned.filtered === 0 || sgv.mgdl < 40) { + } else if (cleaned.filtered === 0 || sgv.mgdl < 40 || prefs.display === 'unfiltered') { raw = cleaned.scale * (cleaned.unfiltered - cleaned.intercept) / cleaned.slope; + } else if (prefs.display === 'filtered') { + raw = cleaned.scale * (cleaned.filtered - cleaned.intercept) / cleaned.slope; } else { var ratio = cleaned.scale * (cleaned.filtered - cleaned.intercept) / cleaned.slope / sgv.mgdl; raw = cleaned.scale * (cleaned.unfiltered - cleaned.intercept) / cleaned.slope / ratio; @@ -96,17 +106,24 @@ function init (ctx) { return display; }; - function alexaRawBGHandler (next, slots, sbx) { - var response = 'Your raw bg is ' + sbx.properties.rawbg.mgdl; - next('Current Raw BG', response); + function virtAsstRawBGHandler (next, slots, sbx) { + if (sbx.properties.rawbg.mgdl) { + var response = translate('virtAsstRawBG', { + params: [ + sbx.properties.rawbg.mgdl + ] + }); + next(translate('virtAsstTitleRawBG'), response); + } else { + next(translate('virtAsstTitleRawBG'), translate('virtAsstUnknown')); + } } - rawbg.alexa = { + rawbg.virtAsst = { intentHandlers: [{ intent: 'MetricNow' - , routableSlot:'metric' - , slots:['raw bg', 'raw blood glucose'] - , intentHandler: alexaRawBGHandler + , metrics:['raw bg', 'raw blood glucose'] + , intentHandler: virtAsstRawBGHandler }] }; diff --git a/lib/plugins/speech.js b/lib/plugins/speech.js index e9032b46644..498071c2a5a 100644 --- a/lib/plugins/speech.js +++ b/lib/plugins/speech.js @@ -1,10 +1,6 @@ 'use strict'; -var levels = require('../levels'); -var times = require('../times'); - var lastEntryValue; -var lastTime; var lastMinutes; var lastEntryTime; @@ -78,7 +74,7 @@ function init(ctx) { lastMinutes = timeMinutes; var lastEntryString = translate('Last entry {0} minutes ago'); - var sayIt = lastEntryString.replace('{0}', timeMinutes); + sayIt = lastEntryString.replace('{0}', timeMinutes); speech.say(sayIt); } } diff --git a/lib/plugins/timeago.js b/lib/plugins/timeago.js index 0e460eb87b0..4b176764d45 100644 --- a/lib/plugins/timeago.js +++ b/lib/plugins/timeago.js @@ -2,18 +2,21 @@ var levels = require('../levels'); var times = require('../times'); +var lastChecked = new Date(); +var lastSuspendTime = new Date("1900-01-01"); -function init (ctx) { +function init(ctx) { var translate = ctx.language.translate; + var heartbeatMs = ctx.settings.heartbeat * 1000; var timeago = { - name: 'timeago' - , label: 'Timeago' - , pluginType: 'pill-status' - , pillFlip: true + name: 'timeago', + label: 'Timeago', + pluginType: 'pill-status', + pillFlip: true }; - timeago.checkNotifications = function checkNotifications (sbx) { + timeago.checkNotifications = function checkNotifications(sbx) { if (!sbx.extendedSettings.enableAlerts) { return; @@ -31,44 +34,69 @@ function init (ctx) { return lines.join('\n'); } - function sendAlarm (opts) { + function sendAlarm(opts) { var agoDisplay = timeago.calcDisplay(lastSGVEntry, sbx.time); sbx.notifications.requestNotify({ - level: opts.level - , title: translate('Stale data, check rig?') - , message: buildMessage(agoDisplay) - , eventName: timeago.name - , plugin: timeago - , group: 'Time Ago' - , pushoverSound: opts.pushoverSound - , debug: agoDisplay + level: opts.level, + title: translate('Stale data, check rig?'), + message: buildMessage(agoDisplay), + eventName: timeago.name, + plugin: timeago, + group: 'Time Ago', + pushoverSound: opts.pushoverSound, + debug: agoDisplay }); } var status = timeago.checkStatus(sbx); if (status === 'urgent') { sendAlarm({ - level: levels.URGENT - , pushoverSound: 'echo' + level: levels.URGENT, + pushoverSound: 'echo' }); } else if (status === 'warn') { sendAlarm({ - level: levels.WARN - , pushoverSound: 'echo' + level: levels.WARN, + pushoverSound: 'echo' }); } }; - timeago.checkStatus = function checkStatus (sbx) { + timeago.checkStatus = function checkStatus(sbx) { + // Check if the app has been suspended; if yes, snooze data missing alarmn for 15 seconds + var now = new Date(); + var delta = now.getTime() - lastChecked.getTime(); + lastChecked = now; + + function isHibernationDetected() { + if (sbx.runtimeEnvironment === 'client') { + if (delta > 15 * 1000) { // Looks like we've been hibernating + lastSuspendTime = now; + } + + var timeSinceLastSuspended = now.getTime() - lastSuspendTime.getTime(); + + return timeSinceLastSuspended < (10 * 1000); + } else if (sbx.runtimeEnvironment === 'server') { + return delta > 2 * heartbeatMs; + } else { + console.error('Cannot detect hibernation, because runtimeEnvironment is not detected from sbx.runtimeEnvironment:', sbx.runtimeEnvironment); + return false; + } + } + + if (isHibernationDetected()) { + console.log('Hibernation detected, suspending timeago alarm'); + return 'current'; + } - var lastSGVEntry = sbx.lastSGVEntry() - , warn = sbx.settings.alarmTimeagoWarn - , warnMins = sbx.settings.alarmTimeagoWarnMins || 15 - , urgent = sbx.settings.alarmTimeagoUrgent - , urgentMins = sbx.settings.alarmTimeagoUrgentMins || 30 - ; + var lastSGVEntry = sbx.lastSGVEntry(), + warn = sbx.settings.alarmTimeagoWarn, + warnMins = sbx.settings.alarmTimeagoWarnMins || 15, + urgent = sbx.settings.alarmTimeagoUrgent, + urgentMins = sbx.settings.alarmTimeagoUrgentMins || 30; function isStale(mins) { return sbx.time - lastSGVEntry.mills > times.mins(mins).msecs; @@ -88,63 +116,60 @@ function init (ctx) { }; - timeago.isMissing = function isMissing (opts) { + timeago.isMissing = function isMissing(opts) { if (!opts || !opts.entry || isNaN(opts.entry.mills) || isNaN(opts.time) || isNaN(opts.timeSince)) { return { - label: translate('time ago') - , shortLabel: translate('ago') + label: translate('time ago'), + shortLabel: translate('ago') }; } }; - timeago.inTheFuture = function inTheFuture (opts) { + timeago.inTheFuture = function inTheFuture(opts) { if (opts.entry.mills - times.mins(5).msecs > opts.time) { return { - label: translate('in the future') - , shortLabel: translate('future') + label: translate('in the future'), + shortLabel: translate('future') }; } }; - timeago.almostInTheFuture = function almostInTheFuture (opts) { + timeago.almostInTheFuture = function almostInTheFuture(opts) { if (opts.entry.mills > opts.time) { return { - value: 1 - , label: translate('min ago') - , shortLabel: 'm' + value: 1, + label: translate('min ago'), + shortLabel: 'm' }; } }; - timeago.isLessThan = function isLessThan (limit, divisor, label, shortLabel) { - return function checkIsLessThan (opts) { + timeago.isLessThan = function isLessThan(limit, divisor, label, shortLabel) { + return function checkIsLessThan(opts) { if (opts.timeSince < limit) { return { - value: Math.max(1, Math.round(opts.timeSince / divisor)) - , label: label - , shortLabel: shortLabel + value: Math.max(1, Math.round(opts.timeSince / divisor)), + label: label, + shortLabel: shortLabel }; } }; }; timeago.resolvers = [ - timeago.isMissing - , timeago.inTheFuture - , timeago.almostInTheFuture - , timeago.isLessThan(times.mins(2).msecs, times.min().msecs, 'min ago', 'm') - , timeago.isLessThan(times.hour().msecs, times.min().msecs, 'mins ago', 'm') - , timeago.isLessThan(times.hours(2).msecs, times.hour().msecs, 'hour ago', 'h') - , timeago.isLessThan(times.day().msecs, times.hour().msecs, 'hours ago', 'h') - , timeago.isLessThan(times.days(2).msecs, times.day().msecs, 'day ago', 'd') - , timeago.isLessThan(times.week().msecs, times.day().msecs, 'days ago', 'd') - , function ( ) { return { label: 'long ago', shortLabel: 'ago' } } + timeago.isMissing, timeago.inTheFuture, timeago.almostInTheFuture, timeago.isLessThan(times.mins(2).msecs, times.min().msecs, 'min ago', 'm'), timeago.isLessThan(times.hour().msecs, times.min().msecs, 'mins ago', 'm'), timeago.isLessThan(times.hours(2).msecs, times.hour().msecs, 'hour ago', 'h'), timeago.isLessThan(times.day().msecs, times.hour().msecs, 'hours ago', 'h'), timeago.isLessThan(times.days(2).msecs, times.day().msecs, 'day ago', 'd'), timeago.isLessThan(times.week().msecs, times.day().msecs, 'days ago', 'd'), + function () { + return { + label: 'long ago', + shortLabel: 'ago' + } + } ]; - timeago.calcDisplay = function calcDisplay (entry, time) { + timeago.calcDisplay = function calcDisplay(entry, time) { var opts = { - time: time - , entry: entry + time: time, + entry: entry }; if (time && entry && entry.mills) { @@ -159,15 +184,16 @@ function init (ctx) { } }; - timeago.updateVisualisation = function updateVisualisation (sbx) { + timeago.updateVisualisation = function updateVisualisation(sbx) { var agoDisplay = timeago.calcDisplay(sbx.lastSGVEntry(), sbx.time); var inRetroMode = sbx.data.inRetroMode; sbx.pluginBase.updatePillText(timeago, { - value: inRetroMode ? null : agoDisplay.value - , label: inRetroMode ? translate('RETRO') : translate(agoDisplay.label) - //no warning/urgent class when in retro mode - , pillClass: inRetroMode ? 'current' : timeago.checkStatus(sbx) + value: inRetroMode ? null : agoDisplay.value, + label: inRetroMode ? translate('RETRO') : translate(agoDisplay.label) + //no warning/urgent class when in retro mode + , + pillClass: inRetroMode ? 'current' : timeago.checkStatus(sbx) }); }; diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index e108dfd0ca7..abb47c78dae 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -1,14 +1,15 @@ 'use strict'; -var _ = require('lodash'); -var times = require('../times'); -var simplealarms = require('./simplealarms')(); +const _ = require('lodash'); +const times = require('../times'); +const simplealarms = require('./simplealarms')(); +const crypto = require('crypto'); -var MANUAL_TREATMENTS = ['BG Check', 'Meal Bolus', 'Carb Correction', 'Correction Bolus']; +const MANUAL_TREATMENTS = ['BG Check', 'Meal Bolus', 'Carb Correction', 'Correction Bolus']; function init() { - var treatmentnotify = { + const treatmentnotify = { name: 'treatmentnotify' , label: 'Treatment Notifications' , pluginType: 'notification' @@ -102,26 +103,32 @@ function init() { if (lastTreatment.isAnnouncement) { requestAnnouncementNotify(lastTreatment, sbx); } else { - var message = buildTreatmentMessage(lastTreatment, sbx); + let message = buildTreatmentMessage(lastTreatment, sbx); - var eventType = lastTreatment.eventType; + let eventType = lastTreatment.eventType; if (lastTreatment.duration === 0 && eventType === 'Temporary Target') { eventType += ' Cancel'; message = translate('Canceled'); } - var timestamp = lastTreatment.timestamp; + const timestamp = lastTreatment.timestamp; if (!message) { message = '...'; } + + const hash = crypto.createHash('sha1'); + const info = JSON.stringify({ eventType, timestamp}); + hash.update(info); + const notifyhash = hash.digest('hex'); sbx.notifications.requestNotify({ level: sbx.levels.INFO , title: translate(eventType) - , message: message - , timestamp: timestamp + , message + , timestamp , plugin: treatmentnotify + , notifyhash }); } } diff --git a/lib/plugins/upbat.js b/lib/plugins/upbat.js index eda42a3901f..dc603054ecb 100644 --- a/lib/plugins/upbat.js +++ b/lib/plugins/upbat.js @@ -4,7 +4,8 @@ var _ = require('lodash'); var times = require('../times'); var levels = require('../levels'); -function init() { +function init(ctx) { + var translate = ctx.language.translate; var upbat = { name: 'upbat' @@ -221,16 +222,32 @@ function init() { }); }; - function alexaUploaderBatteryHandler (next, slots, sbx) { - var response = 'Your uploader battery is at ' + sbx.properties.upbat.display; - next('Uploader battery', response); + function virtAsstUploaderBatteryHandler (next, slots, sbx) { + if (sbx.properties.upbat.display) { + var response = translate('virtAsstUploaderBattery', { + params: [ + sbx.properties.upbat.display + ] + }); + next(translate('virtAsstTitleUploaderBattery'), response); + } else { + next(translate('virtAsstTitleUploaderBattery'), translate('virtAsstUnknown')); + } } - upbat.alexa = { - intentHandlers: [{ - intent: 'UploaderBattery' - , intentHandler: alexaUploaderBatteryHandler - }] + upbat.virtAsst = { + intentHandlers: [ + { + // for backwards compatibility + intent: 'UploaderBattery' + , intentHandler: virtAsstUploaderBatteryHandler + } + , { + intent: 'MetricNow' + , metrics: ['uploader battery'] + , intentHandler: virtAsstUploaderBatteryHandler + } + ] }; return upbat; diff --git a/lib/plugins/xdrip-js.js b/lib/plugins/xdripjs.js similarity index 95% rename from lib/plugins/xdrip-js.js rename to lib/plugins/xdripjs.js index 677d2b42674..a36a42de2e0 100644 --- a/lib/plugins/xdrip-js.js +++ b/lib/plugins/xdripjs.js @@ -11,7 +11,7 @@ function init(ctx) { var lastStateNotification = null; var sensorState = { - name: 'xdrip-js' + name: 'xdripjs' , label: 'CGM Status' , pluginType: 'pill-status' }; @@ -25,7 +25,7 @@ function init(ctx) { if (firstPrefs) { firstPrefs = false; - console.info('xdrip-js Prefs:', prefs); + console.info('xdripjs Prefs:', prefs); } return prefs; @@ -154,8 +154,8 @@ function init(ctx) { }; } - message = 'CGM state: ' + sensorInfo.xdripjs.stateString; - title = 'CGM state: ' + sensorInfo.xdripjs.stateString; + message = 'CGM Transmitter state: ' + sensorInfo.xdripjs.stateString; + title = 'CGM Transmitter state: ' + sensorInfo.xdripjs.stateString; if (sensorInfo.xdripjs.state == 0x7) { // If it is a calibration request, only use INFO @@ -167,15 +167,15 @@ function init(ctx) { if (sensorInfo.xdripjs.voltagea && (sensorInfo.xdripjs.voltagea < prefs.warnBatV)) { sendNotification = true; - message = 'CGM Battery A Low Voltage: ' + sensorInfo.xdripjs.voltagea; - title = 'CGM Battery Low'; + message = 'CGM Transmitter Battery A Low Voltage: ' + sensorInfo.xdripjs.voltagea; + title = 'CGM Transmitter Battery Low'; result.level = levels.WARN; } - if (sensorInfo.xdripjs.voltageb && (sensorInfo.xdripjs.voltageb < prefs.warnBatV)) { + if (sensorInfo.xdripjs.voltageb && (sensorInfo.xdripjs.voltageb < (prefs.warnBatV - 10))) { sendNotification = true; - message = 'CGM Battery B Low Voltage: ' + sensorInfo.xdripjs.voltageb; - title = 'CGM Battery Low'; + message = 'CGM Transmitter Battery B Low Voltage: ' + sensorInfo.xdripjs.voltageb; + title = 'CGM Transmitter Battery Low'; result.level = levels.WARN; } diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index 94120d72f67..5826e6108b4 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -2,31 +2,36 @@ var _ = require('lodash'); var moment = require('moment-timezone'); -var NodeCache = require('node-cache'); +var c = require('memory-cache'); var times = require('./times'); -var crypto = require('crypto'); +var cacheTTL = 5000; var prevBasalTreatment = null; +var cache = new c.Cache(); function init (profileData) { - var profile = { }; + var profile = {}; + + profile.clear = function clear() { + cache.clear(); + profile.data = null; + prevBasalTreatment = null; + } - profile.timeValueCache = new NodeCache({ stdTTL: 600, checkperiod: 600 }); - profile.loadData = function loadData (profileData) { if (profileData && profileData.length) { - profile.data = profile.convertToProfileStore(profileData); + profile.data = profile.convertToProfileStore(profileData); _.each(profile.data, function eachProfileRecord (record) { _.each(record.store, profile.preprocessProfileOnLoad); record.mills = new Date(record.startDate).getTime(); }); } }; - + profile.convertToProfileStore = function convertToProfileStore (dataArray) { var convertedProfiles = []; - _.each(dataArray, function (profile) { + _.each(dataArray, function(profile) { if (!profile.defaultProfile) { var newObject = {}; newObject.defaultProfile = 'Default'; @@ -50,13 +55,13 @@ function init (profileData) { profile.timeStringToSeconds = function timeStringToSeconds (time) { var split = time.split(':'); - return parseInt(split[0])*3600 + parseInt(split[1])*60; + return parseInt(split[0]) * 3600 + parseInt(split[1]) * 60; }; // preprocess the timestamps to seconds for a couple orders of magnitude faster operation profile.preprocessProfileOnLoad = function preprocessProfileOnLoad (container) { _.each(container, function eachValue (value) { - if( Object.prototype.toString.call(value) === '[object Array]' ) { + if (Object.prototype.toString.call(value) === '[object Array]') { profile.preprocessProfileOnLoad(value); } @@ -66,32 +71,31 @@ function init (profileData) { } }); }; - + profile.getValueByTime = function getValueByTime (time, valueType, spec_profile) { if (!time) { time = Date.now(); } + //round to the minute for better caching + var minuteTime = Math.round(time / 60000) * 60000; + var cacheKey = (minuteTime + valueType + spec_profile); + var returnValue = cache.get(cacheKey); + + if (returnValue) { + return returnValue; + } + // CircadianPercentageProfile support var timeshift = 0; var percentage = 100; var activeTreatment = profile.activeProfileTreatmentToTime(time); var isCcpProfile = !spec_profile && activeTreatment && activeTreatment.CircadianPercentageProfile; if (isCcpProfile) { - percentage = activeTreatment.percentage; - timeshift = activeTreatment.timeshift; // in hours + percentage = activeTreatment.percentage; + timeshift = activeTreatment.timeshift; // in hours } var offset = timeshift % 24; time = time + offset * times.hours(offset).msecs; - //round to the minute for better caching - var minuteTime = Math.round(time / 60000) * 60000; - - var cacheKey = (minuteTime + valueType + spec_profile + profile.profiletreatments_hash); - var returnValue = profile.timeValueCache[cacheKey]; - - if (returnValue) { - return returnValue; - } - var valueContainer = profile.getCurrentProfile(time, spec_profile)[valueType]; // Assumes the timestamps are in UTC @@ -100,7 +104,7 @@ function init (profileData) { // TODO: Better warnings to user for missing configuration var t = profile.getTimezone(spec_profile) ? moment(minuteTime).tz(profile.getTimezone(spec_profile)) : moment(minuteTime); - + // Convert to seconds from midnight var mmtMidnight = t.clone().startOf('day'); var timeAsSecondsFromMidnight = t.clone().diff(mmtMidnight, 'seconds'); @@ -109,7 +113,7 @@ function init (profileData) { returnValue = valueContainer; - if( Object.prototype.toString.call(valueContainer) === '[object Array]' ) { + if (Object.prototype.toString.call(valueContainer) === '[object Array]') { _.each(valueContainer, function eachValue (value) { if (timeAsSecondsFromMidnight >= value.timeAsSeconds) { returnValue = value.value; @@ -117,35 +121,49 @@ function init (profileData) { }); } - if (returnValue) { - returnValue = parseFloat(returnValue); - if (isCcpProfile) { - switch (valueType) { - case "sens": - case "carbratio": - returnValue = returnValue * 100 / percentage; - break; - case "basal": - returnValue = returnValue * percentage / 100; - break; - } + if (returnValue) { + returnValue = parseFloat(returnValue); + if (isCcpProfile) { + switch (valueType) { + case "sens": + case "carbratio": + returnValue = returnValue * 100 / percentage; + break; + case "basal": + returnValue = returnValue * percentage / 100; + break; } + } } - profile.timeValueCache[cacheKey] = returnValue; + cache.put(cacheKey, returnValue, cacheTTL); return returnValue; }; profile.getCurrentProfile = function getCurrentProfile (time, spec_profile) { - 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; + } + var data = profile.hasData() ? profile.data[0] : null; var timeprofile = spec_profile || profile.activeProfileToTime(time); - return data && data.store[timeprofile] ? data.store[timeprofile] : {}; + returnValue = data && data.store[timeprofile] ? data.store[timeprofile] : {}; + + cache.put(cacheKey, returnValue, cacheTTL); + return returnValue; }; profile.getUnits = function getUnits (spec_profile) { - return profile.getCurrentProfile(null, spec_profile)['units']; + var pu = profile.getCurrentProfile(null, spec_profile)['units'] + ' '; + if (pu.toLowerCase().includes('mmol')) return 'mmol'; + return 'mgdl'; }; profile.getTimezone = function getTimezone (spec_profile) { @@ -185,27 +203,26 @@ function init (profileData) { }; profile.updateTreatments = function updateTreatments (profiletreatments, tempbasaltreatments, combobolustreatments) { - + profile.profiletreatments = profiletreatments || []; profile.tempbasaltreatments = tempbasaltreatments || []; - // dedupe temp basal events + // dedupe temp basal events profile.tempbasaltreatments = _.uniqBy(profile.tempbasaltreatments, 'mills'); - + _.each(profile.tempbasaltreatments, function addDuration (t) { - t.endmills = t.mills + times.mins(t.duration || 0).msecs; + t.endmills = t.mills + times.mins(t.duration || 0).msecs; + }); + + profile.tempbasaltreatments.sort(function compareTreatmentMills (a, b) { + return a.mills - b.mills; }); - - profile.tempbasaltreatments.sort (function compareTreatmentMills (a, b) { - return a.mills - b.mills; - }); profile.combobolustreatments = combobolustreatments || []; - profile.profiletreatments_hash = crypto.createHash('sha1').update(JSON.stringify(profile.profiletreatments)).digest('hex'); - profile.tempbasaltreatments_hash = crypto.createHash('sha1').update(JSON.stringify(profile.tempbasaltreatments)).digest('hex'); - profile.combobolustreatments_hash = crypto.createHash('sha1').update(JSON.stringify(profile.combobolustreatments)).digest('hex'); + + cache.clear(); }; - + profile.activeProfileToTime = function activeProfileToTime (time) { if (profile.hasData()) { var timeprofile = profile.data[0].defaultProfile; @@ -218,11 +235,12 @@ function init (profileData) { } return null; }; - + profile.activeProfileTreatmentToTime = function activeProfileTreatmentToTime (time) { - var cacheKey = 'profile' + time + profile.profiletreatments_hash; - //var returnValue = profile.timeValueCache[cacheKey]; - var returnValue; + + var minuteTime = Math.round(time / 60000) * 60000; + var cacheKey = 'profileCache' + minuteTime; + var returnValue = cache.get(cacheKey); if (returnValue) { return returnValue; @@ -230,86 +248,88 @@ 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; - var json = JSON.parse(treatment.profileJson); - profile.data[0].store[treatment.profile] = json; - } + 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; - var 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; } } + } }); } - + returnValue = treatment; - profile.timeValueCache[cacheKey] = returnValue; + cache.put(cacheKey, returnValue, cacheTTL); return returnValue; }; - profile.profileSwitchName = function profileSwitchName(name) { + profile.profileSwitchName = function profileSwitchName (name) { var index = name.indexOf("@@@@@"); if (index < 0) return name; else return name.substring(0, index); } - + profile.tempBasalTreatment = function tempBasalTreatment (time) { - // Most queries for the data in reporting will match the latest found value, caching that hugely improves performance - if (prevBasalTreatment && time >= prevBasalTreatment.mills && time <= prevBasalTreatment.endmills) { - return prevBasalTreatment; - } - + // Most queries for the data in reporting will match the latest found value, caching that hugely improves performance + if (prevBasalTreatment && time >= prevBasalTreatment.mills && time <= prevBasalTreatment.endmills) { + return prevBasalTreatment; + } + // Binary search for events for O(log n) performance - var first = 0, last = profile.tempbasaltreatments.length - 1; - + var first = 0 + , last = profile.tempbasaltreatments.length - 1; + while (first <= last) { - var i = first + Math.floor((last - first) / 2); - var t = profile.tempbasaltreatments[i]; - if (time >= t.mills && time <= t.endmills) { - prevBasalTreatment = t; - return t; - } - if (time < t.mills) { - last = i - 1; - } else { - first = i + 1; - } + var i = first + Math.floor((last - first) / 2); + var t = profile.tempbasaltreatments[i]; + if (time >= t.mills && time <= t.endmills) { + prevBasalTreatment = t; + return t; + } + if (time < t.mills) { + last = i - 1; + } else { + first = i + 1; + } } - + return null; }; profile.comboBolusTreatment = function comboBolusTreatment (time) { var treatment = null; - profile.combobolustreatments.forEach( function eachTreatment (t) { - var duration = times.mins(t.duration || 0).msecs; - if (time < t.mills + duration && time > t.mills) { - treatment = t; - } + profile.combobolustreatments.forEach(function eachTreatment (t) { + var duration = times.mins(t.duration || 0).msecs; + if (time < t.mills + duration && time > t.mills) { + treatment = t; + } }); return treatment; }; profile.getTempBasal = function getTempBasal (time, spec_profile) { - var cacheKey = 'basal' + time + profile.tempbasaltreatments_hash + profile.combobolustreatments_hash + profile.profiletreatments_hash + spec_profile; - var returnValue = profile.timeValueCache[cacheKey]; + var minuteTime = Math.round(time / 60000) * 60000; + var cacheKey = 'basalCache' + minuteTime + spec_profile; + var returnValue = cache.get(cacheKey); if (returnValue) { return returnValue; @@ -326,7 +346,7 @@ function init (profileData) { tempbasal = Number(treatment.absolute); } else if (treatment && treatment.percent) { tempbasal = basal * (100 + treatment.percent) / 100; - } + } if (combobolustreatment && combobolustreatment.relative) { combobolusbasal = combobolustreatment.relative; } @@ -338,7 +358,7 @@ function init (profileData) { , combobolusbasal: combobolusbasal , totalbasal: tempbasal + combobolusbasal }; - profile.timeValueCache[cacheKey] = returnValue; + cache.put(cacheKey, returnValue, cacheTTL); return returnValue; }; @@ -347,30 +367,14 @@ function init (profileData) { if (profile.hasData()) { var current = profile.activeProfileToTime(); profiles.push(current); - - for (var key in profile.data[0].store) { - if (profile.data[0].store.hasOwnProperty(key) && key !== current) { - if (key.indexOf('@@@@@') < 0) - profiles.push(key); - } - } - } - return profiles; - }; - // get original store without added profiles fro profileSwitches - profile.getProfileStore = function getProfileStore () { - var newprofiledata = _.clone(profile.data[0]); - for (var key in profile.data[0].store) { - if (profile.data[0].store.hasOwnProperty(key)) { - if (key.indexOf('@@@@@') < 0) - store[key] = profile.data[0].store[key]; - } + Object.keys(profile.data[0].store).forEach(key => { + if (key !== current && key.indexOf('@@@@@') < 0) profiles.push(key); + }) } - return store; + return profiles; }; - if (profileData) { profile.loadData(profileData); } // init treatments array profile.updateTreatments([], []); @@ -378,4 +382,4 @@ function init (profileData) { return profile; } -module.exports = init; \ No newline at end of file +module.exports = init; diff --git a/lib/report_plugins/calibrations.js b/lib/report_plugins/calibrations.js index f7088080690..baf16a47a27 100644 --- a/lib/report_plugins/calibrations.js +++ b/lib/report_plugins/calibrations.js @@ -8,30 +8,29 @@ var calibrations = { , pluginType: 'report' }; -function init() { +function init () { return calibrations; } module.exports = init; -calibrations.html = function html(client) { +calibrations.html = function html (client) { var translate = client.translate; var ret = - '

' + translate('Calibrations') + '

' - + '
' - + '
' - ; + '

' + translate('Calibrations') + '

' + + '
' + + '
'; return ret; }; -calibrations.report = function report_calibrations(datastorage,sorteddaystoshow) { +calibrations.report = function report_calibrations (datastorage, sorteddaystoshow) { var Nightscout = window.Nightscout; var report_plugins = Nightscout.report_plugins; var padding = { top: 15, right: 15, bottom: 30, left: 70 }; var treatments = []; - sorteddaystoshow.forEach(function (day) { - treatments = treatments.concat(datastorage[day].treatments.filter(function (t) { + sorteddaystoshow.forEach(function(day) { + treatments = treatments.concat(datastorage[day].treatments.filter(function(t) { if (t.eventType === 'Sensor Start') { return true; } @@ -43,59 +42,58 @@ calibrations.report = function report_calibrations(datastorage,sorteddaystoshow) }); var cals = []; - sorteddaystoshow.forEach(function (day) { + sorteddaystoshow.forEach(function(day) { cals = cals.concat(datastorage[day].cal); }); var sgvs = []; - sorteddaystoshow.forEach(function (day) { + sorteddaystoshow.forEach(function(day) { sgvs = sgvs.concat(datastorage[day].sgv); }); - + var mbgs = []; - sorteddaystoshow.forEach(function (day) { + sorteddaystoshow.forEach(function(day) { mbgs = mbgs.concat(datastorage[day].mbg); }); - mbgs.forEach(function (mbg) { calcmbg(mbg); }); - + mbgs.forEach(function(mbg) { calcmbg(mbg); }); var events = treatments.concat(cals).concat(mbgs).sort(function(a, b) { return a.mills - b.mills; }); - - var colors = ['Aqua','Blue','Brown','Chartreuse','Coral','CornflowerBlue','DarkCyan','DarkMagenta','DarkOrange','Fuchsia','Green','Yellow']; + + var colors = ['Aqua', 'Blue', 'Brown', 'Chartreuse', 'Coral', 'CornflowerBlue', 'DarkCyan', 'DarkMagenta', 'DarkOrange', 'Fuchsia', 'Green', 'Yellow']; var colorindex = 0; var html = ''; var lastmbg = null; - for (var i=0; i'; - }; - + } + html += '
'; + html += '' + report_plugins.utils.localeDateTime(new Date(e.mills)) + ''; e.bgcolor = colors[colorindex]; if (e.eventType) { - html += ''+translate(e.eventType)+':
'; + html += '' + translate(e.eventType) + ':
'; } else if (typeof e.device !== 'undefined') { - html += ' '; - html += 'MBG: ' + e.y + ' Raw: '+e.raw+'
'; + html += ' '; + html += 'MBG: ' + e.y + ' Raw: ' + e.raw + '
'; lastmbg = e; e.cals = []; e.checked = false; } else if (typeof e.scale !== 'undefined') { html += 'CAL: ' + ' Scale: ' + e.scale.toFixed(2) + ' Intercept: ' + e.intercept.toFixed(0) + ' Slope: ' + e.slope.toFixed(2) + '
'; - if (lastmbg) { + if (lastmbg) { lastmbg.cals.push(e); } } else { html += JSON.stringify(e); } html += '
'; $('#calibrations-list').html(html); - + // select last 3 mbgs checkLastCheckboxes(3); drawelements(); @@ -103,27 +101,27 @@ calibrations.report = function report_calibrations(datastorage,sorteddaystoshow) $('.calibrations-checkbox').change(checkboxevent); function checkLastCheckboxes (maxcals) { - for (i=events.length-1; i>0; i--) { + for (i = events.length - 1; i > 0; i--) { if (typeof events[i].device !== 'undefined') { events[i].checked = true; - $('#calibrations-'+i).prop('checked',true); - if (--maxcals<1) { + $('#calibrations-' + i).prop('checked', true); + if (--maxcals < 1) { break; } } } } - - function checkboxevent(event) { + + function checkboxevent (event) { var index = $(this).attr('index'); events[index].checked = $(this).is(':checked'); drawelements(); event.preventDefault(); } - function drawelements() { + function drawelements () { drawChart(); - for (var i=0; i5*60*1000) { - console.log('Last SGV too old for MBG. Time diff: '+((mbg.mills-lastsgv.mills)/1000/60).toFixed(1)+' min',mbg); + if (mbg.mills - lastsgv.mills > 5 * 60 * 1000) { + console.log('Last SGV too old for MBG. Time diff: ' + ((mbg.mills - lastsgv.mills) / 1000 / 60).toFixed(1) + ' min', mbg); } else { mbg.raw = lastsgv.filtered || lastsgv.unfiltered; } } else { - console.log('Last entry not found for MBG ',mbg); + console.log('Last entry not found for MBG ', mbg); } } - - function drawmbg(mbg) { + + function drawmbg (mbg) { var color = mbg.bgcolor; if (mbg.raw) { calibration_context.append('circle') @@ -267,11 +261,11 @@ calibrations.report = function report_calibrations(datastorage,sorteddaystoshow) .attr('r', 5); } } - - function findlatest(date,storage) { + + function findlatest (date, storage) { var last = null; var time = date.getTime(); - for (var i=0; i time) { return last; } diff --git a/lib/report_plugins/dailystats.js b/lib/report_plugins/dailystats.js index 4cfd99f8450..2f9fa77a24a 100644 --- a/lib/report_plugins/dailystats.js +++ b/lib/report_plugins/dailystats.js @@ -6,41 +6,39 @@ var dailystats = { , pluginType: 'report' }; -function init() { +function init () { return dailystats; } module.exports = init; -dailystats.html = function html(client) { +dailystats.html = function html (client) { var translate = client.translate; var ret = - '

' + translate('Daily stats report') + '

' - + '
' - ; + '

' + translate('Daily stats report') + '

' + + '
'; return ret; }; dailystats.css = - '#dailystats-placeholder .tdborder {' - + ' width:80px;' - + ' border: 1px #ccc solid;' - + ' margin: 0;' - + ' padding: 1px;' - + ' text-align:center;' - + '}' - + '#dailystats-placeholder .inlinepiechart {' - + ' width: 2.0in;' - + ' height: 0.9in;' - + '}' - ; - -dailystats.report = function report_dailystats(datastorage,sorteddaystoshow,options) { + '#dailystats-placeholder .tdborder {' + + ' width:80px;' + + ' border: 1px #ccc solid;' + + ' margin: 0;' + + ' padding: 1px;' + + ' text-align:center;' + + '}' + + '#dailystats-placeholder .inlinepiechart {' + + ' width: 2.0in;' + + ' height: 0.9in;' + + '}'; + +dailystats.report = function report_dailystats (datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; var report_plugins = Nightscout.report_plugins; - + var ss = require('simple-statistics'); var todo = []; @@ -52,31 +50,31 @@ dailystats.report = function report_dailystats(datastorage,sorteddaystoshow,opti report.append(table); var thead = $('
'+translate('Date')+''+translate('Low')+''+translate('Normal')+''+translate('High')+''+translate('Readings')+''+translate('Min')+''+translate('Max')+''+translate('Average')+''+translate('StDev')+''+translate('25%')+''+translate('Median')+''+translate('75%')+'' + translate('Date') + '' + translate('Low') + '' + translate('Normal') + '' + translate('High') + '' + translate('Readings') + '' + translate('Min') + '' + translate('Max') + '' + translate('Average') + '' + translate('StDev') + '' + translate('25%') + '' + translate('Median') + '' + translate('75%') + '
').appendTo(tr); - $('' + report_plugins.utils.localeDate(day) + ''+translate('No data available')+'' + report_plugins.utils.localeDate(day) + '' + translate('No data available') + '
' + report_plugins.utils.localeDate(day) + '' + Math.round((100 * stats.lows) / daysRecords.length) + '%' + Math.round((100 * stats.normal) / daysRecords.length) + '%' + Math.round((100 * stats.highs) / daysRecords.length) + '%' + daysRecords.length +'' + minForDay +'' + maxForDay +'' + average.toFixed(1) +'' + ss.standard_deviation(bgValues).toFixed(1) + '' + ss.quantile(bgValues, 0.25).toFixed(1) + '' + ss.quantile(bgValues, 0.5).toFixed(1) + '' + ss.quantile(bgValues, 0.75).toFixed(1) + '
' + report_plugins.utils.localeDate(day) + '' + Math.round((100 * stats.lows) / daysRecords.length) + '%' + Math.round((100 * stats.normal) / daysRecords.length) + '%' + Math.round((100 * stats.highs) / daysRecords.length) + '%' + daysRecords.length + '' + minForDay + '' + maxForDay + '' + average.toFixed(1) + '' + ss.standard_deviation(bgValues).toFixed(1) + '' + ss.quantile(bgValues, 0.25).toFixed(1) + '' + ss.quantile(bgValues, 0.5).toFixed(1) + '' + ss.quantile(bgValues, 0.75).toFixed(1) + '
')); }); }; -daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) { +daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; var profile = client.sbx.data.profile; var report_plugins = Nightscout.report_plugins; var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; - - var TOOLTIP_TRANS_MS = 300; var padding = { top: 15, right: 22, bottom: 30, left: 35 }; var tddSum = 0; + var basalSum = 0; + var baseBasalSum = 0; + var bolusSum = 0; var carbsSum = 0; + var proteinSum = 0; + var fatSum = 0; - daytoday.prepareHtml(sorteddaystoshow) ; - sorteddaystoshow.forEach( function eachDay(day) { - drawChart(day,datastorage[day],options); + daytoday.prepareHtml(sorteddaystoshow); + sorteddaystoshow.forEach(function eachDay (day) { + drawChart(day, datastorage[day], options); }); var tddAverage = tddSum / datastorage.alldays; + var basalAveragePercent = Math.round( (basalSum / datastorage.alldays) / tddAverage * 100); + var baseBasalAveragePercent = Math.round( (baseBasalSum / datastorage.alldays) / tddAverage * 100); + var bolusAveragePercent = Math.round( (bolusSum / datastorage.alldays) / tddAverage * 100); var carbsAverage = carbsSum / datastorage.alldays; - - if (options.insulindistribution) - $('#daytodaycharts').append('

' + translate('TDD average') + ': ' + tddAverage.toFixed(1) + 'U ' + translate('Carbs average') + ': ' + carbsAverage.toFixed(0) + 'g'); + var proteinAverage = proteinSum / datastorage.alldays; + var fatAverage = fatSum / datastorage.alldays; + + if (options.insulindistribution) { + var html = '

' + translate('TDD average') + ': ' + tddAverage.toFixed(1) + 'U  '; + html += '' + translate('Bolus average') + ': ' + bolusAveragePercent + '%  '; + html += '' + translate('Basal average') + ': ' + basalAveragePercent + '%  '; + html += '(' + translate('Base basal average:') + ' ' + baseBasalAveragePercent + '%)  '; + html += '' + translate('Carbs average') + ': ' + carbsAverage.toFixed(0) + 'g'; + html += '' + translate('Protein average') + ': ' + proteinAverage.toFixed(0) + 'g'; + html += '' + translate('Fat average') + ': ' + fatAverage.toFixed(0) + 'g'; + $('#daytodaycharts').append(html); + } function timeTicks(n,i) { var t12 = [ @@ -97,28 +127,28 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) return t12[i]; } } - - function drawChart(day,data,options) { + + function drawChart (day, data, options) { var tickValues , charts , context , xScale2, yScale2 , yInsulinScale, yCarbsScale, yScaleBasals , xAxis2, yAxis2 - , dateFn = function (d) { return new Date(d.date); } + , dateFn = function(d) { return new Date(d.date); } , foodtexts = 0; - tickValues = client.ticks(client, { - scaleY: options.scale === report_plugins.consts.SCALE_LOG ? 'log' : 'linear' - , targetTop: options.targetHigh - , targetBottom: options.targetLow - }); - - // add defs for combo boluses - var dashWidth = 5; - d3.select('body').append('svg') - .append('defs') - .append('pattern') + tickValues = client.ticks(client, { + scaleY: options.scale === report_plugins.consts.SCALE_LOG ? 'log' : 'linear' + , targetTop: options.targetHigh + , targetBottom: options.targetLow + }); + + // add defs for combo boluses + var dashWidth = 5; + d3.select('body').append('svg') + .append('defs') + .append('pattern') .attr('id', 'hash') .attr('patternUnits', 'userSpaceOnUse') .attr('width', 6) @@ -126,58 +156,54 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) .attr('x', 0) .attr('y', 0) .append('g') - .style('fill', 'none') - .style('stroke', '#0099ff') - .style('stroke-width', 2) + .style('fill', 'none') + .style('stroke', '#0099ff') + .style('stroke-width', 2) .append('path').attr('d', 'M0,0 l' + dashWidth + ',' + dashWidth) .append('path').attr('d', 'M' + dashWidth + ',0 l-' + dashWidth + ',' + dashWidth); // create svg and g to contain the chart contents charts = d3.select('#daytodaychart-' + day).html( - ''+ - report_plugins.utils.localeDate(day)+ + '' + + report_plugins.utils.localeDate(day) + '
' - ).append('svg'); + ).append('svg'); charts.append('rect') .attr('width', '100%') .attr('height', '100%') .attr('fill', 'WhiteSmoke'); - + context = charts.append('g'); // define the parts of the axis that aren't dependent on width or height - xScale2 = d3.time.scale() + xScale2 = d3.scaleTime() .domain(d3.extent(data.sgv, dateFn)); if (options.scale === report_plugins.consts.SCALE_LOG) { - yScale2 = d3.scale.log() + yScale2 = d3.scaleLog() .domain([client.utils.scaleMgdl(options.basal ? 30 : 36), client.utils.scaleMgdl(420)]); } else { - yScale2 = d3.scale.linear() + yScale2 = d3.scaleLinear() .domain([client.utils.scaleMgdl(options.basal ? -40 : 36), client.utils.scaleMgdl(420)]); } // allow insulin to be negative (when plotting negative IOB) - yInsulinScale = d3.scale.linear() + yInsulinScale = d3.scaleLinear() .domain([-2 * options.maxInsulinValue, 2 * options.maxInsulinValue]); - yCarbsScale = d3.scale.linear() - .domain([0, options.maxCarbsValue*1.25]); + yCarbsScale = d3.scaleLinear() + .domain([0, options.maxCarbsValue * 1.25]); + + yScaleBasals = d3.scaleLinear(); - yScaleBasals = d3.scale.linear(); - - xAxis2 = d3.svg.axis() - .scale(xScale2) + xAxis2 = d3.axisBottom(xScale2) .tickFormat(timeTicks) - .ticks(24) - .orient('bottom'); + .ticks(24); - yAxis2 = d3.svg.axis() - .scale(yScale2) + yAxis2 = d3.axisLeft(yScale2) .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); + .tickValues(tickValues); // get current data range var dataRange = d3.extent(data.sgv, dateFn); @@ -189,20 +215,20 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) //set the width and height of the SVG element charts.attr('width', options.width) .attr('height', options.height); - + // ranges are based on the width and height available so reset xScale2.range([0, chartWidth]); - yScale2.range([chartHeight,0]); + yScale2.range([chartHeight, 0]); yInsulinScale.range([chartHeight * 2, 0]); - yCarbsScale.range([chartHeight,0]); + yCarbsScale.range([chartHeight, 0]); yScaleBasals.range([yScale2(client.utils.scaleMgdl(72)), chartHeight]); // add target BG rect context.append('rect') - .attr('x', xScale2(dataRange[0])+padding.left) - .attr('y', yScale2(options.targetHigh)+padding.top) - .attr('width', xScale2(dataRange[1]- xScale2(dataRange[0]))) - .attr('height', yScale2(options.targetLow)-yScale2(options.targetHigh)) + .attr('x', xScale2(dataRange[0]) + padding.left) + .attr('y', yScale2(options.targetHigh) + padding.top) + .attr('width', xScale2(dataRange[1] - xScale2(dataRange[0]))) + .attr('height', yScale2(options.targetLow) - yScale2(options.targetHigh)) .style('fill', '#D6FFD6') .attr('stroke', 'grey'); @@ -229,68 +255,175 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) .style('fill', 'none') .call(xAxis2); - _.each(tickValues, function (n, li) { + _.each(tickValues, function(n, li) { context.append('line') .attr('class', 'high-line') - .attr('x1', xScale2(dataRange[0])+padding.left) - .attr('y1', yScale2(tickValues[li])+padding.top) - .attr('x2', xScale2(dataRange[1])+padding.left) - .attr('y2', yScale2(tickValues[li])+padding.top) + .attr('x1', xScale2(dataRange[0]) + padding.left) + .attr('y1', yScale2(tickValues[li]) + padding.top) + .attr('x2', xScale2(dataRange[1]) + padding.left) + .attr('y2', yScale2(tickValues[li]) + padding.top) .style('stroke-dasharray', ('1, 5')) .attr('stroke', 'grey'); }); - // bind up the context chart data to an array of circles - var contextCircles = context.selectAll('circle') - .data(data.sgv); - - function prepareContextCircles(sel) { + function prepareContextCircles (sel) { var badData = []; - sel.attr('cx', function (d) { - return xScale2(d.date) + padding.left; + sel.attr('cx', function(d) { + return xScale2(d.date) + padding.left; }) - .attr('cy', function (d) { - if (isNaN(d.sgv)) { - badData.push(d); - return yScale2(client.utils.scaleMgdl(450) + padding.top); - } else { - return yScale2(d.sgv) + padding.top; - } - }) - .attr('fill', function (d) { - if (d.color === 'gray' && !options.raw) { - return 'transparent'; - } - return d.color; - }) - .style('opacity', function () { return 0.5 }) - .attr('stroke-width', function (d) {if (d.type === 'mbg') { return 2; } else if (options.openAps && d.openaps) { return 1; } else { return 0; }}) - .attr('stroke', function () { return 'black'; }) - .attr('r', function(d) { - if (d.type === 'mbg') { - return 4; - } else { - return 2 + (options.width - 800) / 400; + .attr('cy', function(d) { + if (isNaN(d.sgv)) { + badData.push(d); + return yScale2(client.utils.scaleMgdl(450) + padding.top); + } else { + return yScale2(d.sgv) + padding.top; + } + }) + .attr('fill', function(d) { + if (d.color === 'gray' && !options.raw) { + return 'transparent'; + } + return d.color; + }) + .style('opacity', function() { return 0.5 }) + .attr('stroke-width', function(d) { if (d.type === 'mbg') { return 2; } else if (options.openAps && d.openaps) { return 1; } else { return 0; } }) + .attr('stroke', function() { return 'black'; }) + .attr('r', function(d) { + if (d.type === 'mbg') { + return 4; + } else { + return 2 + (options.width - 800) / 400; + } + }) + .on('mouseover', function(d) { + if (options.openAps && d.openaps) { + client.tooltip.style('opacity', .9); + var text = 'BG: ' + d.openaps.suggested.bg + + ', ' + d.openaps.suggested.reason + + (d.openaps.suggested.mealAssist ? ' Meal Assist: ' + d.openaps.suggested.mealAssist : ''); + client.tooltip.html(text) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + } + }) + .on('mouseout', hideTooltip); + + if (badData.length > 0) { + console.warn('Bad Data: isNaN(sgv)', badData); + } + return sel; + } + + // PREDICTIONS START + // + function preparePredictedData () { + + var treatmentsTimestamps = []; // Only timestamps for (carbs and bolus insulin) treatments will be captured in this array + treatmentsTimestamps.push(dataRange[0]); // Create a fake timestamp at midnight so we can show predictions during night + for (var i in data.treatments) { + var treatment = data.treatments[i]; + if (undefined != treatment.carbs && null != treatment.carbs && treatment.carbs > 0) { + if (treatment.timestamp) + treatmentsTimestamps.push(treatment.timestamp); + else if (treatment.created_at) + treatmentsTimestamps.push(treatment.created_at); + } + if (undefined != treatment.insulin && null != treatment.insulin && treatment.insulin > 0) { + if (treatment.timestamp) + treatmentsTimestamps.push(treatment.timestamp); + else if (treatment.created_at) + treatmentsTimestamps.push(treatment.created_at); + } + } + + var predictions = []; + if (data && data.devicestatus) { + for (i = data.devicestatus.length - 1; i >= 0; i--) { + if (data.devicestatus[i].loop && data.devicestatus[i].loop.predicted) { + predictions.push(data.devicestatus[i].loop.predicted); + } else if (data.devicestatus[i].openaps && data.devicestatus[i].openaps.suggested && data.devicestatus[i].openaps.suggested.predBGs) { + var entry = {}; + entry.startDate = data.devicestatus[i].openaps.suggested.timestamp; + // For OpenAPS/AndroidAPS we fall back from COB if present, to UAM, then IOB + if (data.devicestatus[i].openaps.suggested.predBGs.COB) { + entry.values = data.devicestatus[i].openaps.suggested.predBGs.COB; + } else if (data.devicestatus[i].openaps.suggested.predBGs.UAM) { + entry.values = data.devicestatus[i].openaps.suggested.predBGs.UAM; + } else entry.values = data.devicestatus[i].openaps.suggested.predBGs.IOB; + predictions.push(entry); + } + } + } + + var p = []; + if (predictions.length > 0 && treatmentsTimestamps.length > 0) { + + // Iterate over all treatments, find the predictions for each and add them to the predicted array p + for (var treatmentsIndex = 0; treatmentsIndex < treatmentsTimestamps.length; treatmentsIndex++) { + var timestamp = treatmentsTimestamps[treatmentsIndex]; + // TODO refactor code so this is set here - now set as global in file loaded by the browser + // eslint-disable-next-line no-undef + var predictedIndex = findPredicted(predictions, timestamp, predictedOffset); // Find predictions offset before or after timestamp + + if (predictedIndex != null) { + entry = predictions[predictedIndex]; // Start entry + var d = moment(entry.startDate); + var end = moment().endOf('day'); + if (options.predictedTruncate) { + // eslint-disable-next-line no-undef + if (predictedOffset >= 0) { + // If we are looking forward we want to stop at the next treatment + if (treatmentsIndex < treatmentsTimestamps.length - 1) { + end = moment(treatmentsTimestamps[treatmentsIndex + 1]); + } + } else { + // If we are looking back, then we want to stop at "this" treatment + end = moment(treatmentsTimestamps[treatmentsIndex]); + } } - }) - .on('mouseover', function (d) { - if (options.openAps && d.openaps) { - client.tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - var text = 'BG: ' + d.openaps.suggested.bg - + ', ' + d.openaps.suggested.reason - + (d.openaps.suggested.mealAssist ? ' Meal Assist: ' + d.openaps.suggested.mealAssist : ''); - client.tooltip.html(text) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); + for (var entryIndex in entry.values) { + if (!d.isAfter(end)) { + var value = {}; + value.sgv = client.utils.scaleMgdl(entry.values[entryIndex]); + value.date = d.toDate(); + value.color = 'purple'; + p.push(value); + d.add(5, 'minutes'); + } } - }) - .on('mouseout', hideTooltip); + } + } + } + return p; + } - if (badData.length > 0) { - console.warn('Bad Data: isNaN(sgv)', badData); + /* Find the earliest new predicted instance that has a timestamp equal to or larger than timestamp */ + /* (so if we have bolused or eaten we want to find the prediction that Loop has estimated just after that) */ + /* Returns the index into the predictions array that is the predicted we are looking for */ + function findPredicted (predictions, timestamp, offset) { + var ts = moment(timestamp).add(offset, 'minutes'); + var predicted = null; + if (offset && offset < 0) { // If offset is negative, start searching from first prediction going forward + for (var i = 0; i < predictions.length; i++) { + if (predictions[i] && predictions[i].startDate && moment(predictions[i].startDate) <= ts) { + predicted = i; + } + } + } else { // If offset is positive or zero, start searching from last prediction going backward + for (i = predictions.length - 1; i > 0; i--) { + if (predictions[i] && predictions[i].startDate && moment(predictions[i].startDate) >= ts) { + predicted = i; } - return sel; + } } + return predicted; + } + // + // PREDICTIONS ENDS + + // bind up the context chart data to an array of circles + var contextData = (options.predicted ? data.sgv.concat(preparePredictedData()) : data.sgv); + var contextCircles = context.selectAll('circle').data(contextData); // if new circle then just display prepareContextCircles(contextCircles.enter().append('circle')); @@ -298,10 +431,11 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) contextCircles.exit() .remove(); - var to = moment(day).add(1, 'days'); - var from = moment(day); - var iobpolyline = '', cobpolyline = ''; - + var to = moment(day).add(1, 'days'); + var from = moment(day); + var iobpolyline = '' + , cobpolyline = ''; + // basals data var linedata = []; var notemplinedata = []; @@ -315,13 +449,13 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) data.netBasalPositive = []; data.netBasalNegative = []; - [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23].forEach(function(hour) { + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23].forEach(function(hour) { data.netBasalPositive[hour] = 0; data.netBasalNegative[hour] = 0; }); profile.updateTreatments(datastorage.profileSwitchTreatments, datastorage.tempbasalTreatments, datastorage.combobolusTreatments); - + var bolusInsulin = 0; var baseBasalInsulin = 0; var positiveTemps = 0; @@ -336,9 +470,9 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) console.log("Device COB status available: ", cobStatusAvailable); console.log("Device IOB status available: ", iobStatusAvailable); - for (var dt=moment(from); dt < to; dt.add(5, 'minutes')) { + for (var dt = moment(from); dt < to; dt.add(5, 'minutes')) { if (options.iob && !iobStatusAvailable) { - var iob = client.plugins('iob').calcTotal(datastorage.treatments,datastorage.devicestatus,profile,dt.toDate()).iob; + var iob = client.plugins('iob').calcTotal(datastorage.treatments, datastorage.devicestatus, profile, dt.toDate()).iob; // make the graph discontinuous when data is missing if (iob === undefined) { iobpolyline += ', ' + (xScale2(lastDt) + padding.left) + ',' + (yInsulinScale(0) + padding.top); @@ -347,19 +481,19 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) if (lastIOB === undefined) { iobpolyline += ', ' + (xScale2(dt) + padding.left) + ',' + (yInsulinScale(0) + padding.top); } - iobpolyline += ', '+ (xScale2(dt) + padding.left) + ',' + (yInsulinScale(iob) + padding.top); + iobpolyline += ', ' + (xScale2(dt) + padding.left) + ',' + (yInsulinScale(iob) + padding.top); } lastDt = dt.clone(); lastIOB = iob; } if (options.cob && !cobStatusAvailable) { - var cob = client.plugins('cob').cobTotal(datastorage.treatments,datastorage.devicestatus,profile,dt.toDate()).cob; + var cob = client.plugins('cob').cobTotal(datastorage.treatments, datastorage.devicestatus, profile, dt.toDate()).cob; if (!dt.isSame(from)) { cobpolyline += ', '; } cobpolyline += (xScale2(dt.toDate()) + padding.left) + ',' + (yCarbsScale(cob) + padding.top) + ' '; } - if (options.basal) { + if (options.basal) { var date = dt.format('x'); var hournow = dt.hour(); var basalvalue = profile.getTempBasal(date); @@ -369,26 +503,27 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) if (tempPart > 0) { positiveTemps += tempPart; data.netBasalPositive[hournow] += tempPart; - } if (tempPart < 0) { + } + if (tempPart < 0) { negativeTemps += tempPart; data.netBasalNegative[hournow] += tempPart; } - + if (!_.isEqual(lastbasal, basalvalue)) { - linedata.push( { d: date, b: basalvalue.totalbasal } ); - notemplinedata.push( { d: date, b: basalvalue.basal } ); + linedata.push({ d: date, b: basalvalue.totalbasal }); + notemplinedata.push({ d: date, b: basalvalue.basal }); if (basalvalue.combobolustreatment && basalvalue.combobolustreatment.relative) { - tempbasalareadata.push( { d: date, b: basalvalue.tempbasal } ); - basalareadata.push( { d: date, b: 0 } ); - comboareadata.push( { d: date, b: basalvalue.totalbasal } ); + tempbasalareadata.push({ d: date, b: basalvalue.tempbasal }); + basalareadata.push({ d: date, b: 0 }); + comboareadata.push({ d: date, b: basalvalue.totalbasal }); } else if (basalvalue.treatment) { - tempbasalareadata.push( { d: date, b: basalvalue.totalbasal } ); - basalareadata.push( { d: date, b: 0 } ); - comboareadata.push( { d: date, b: 0 } ); + tempbasalareadata.push({ d: date, b: basalvalue.totalbasal }); + basalareadata.push({ d: date, b: 0 }); + comboareadata.push({ d: date, b: 0 }); } else { - tempbasalareadata.push( { d: date, b: 0 } ); - basalareadata.push( { d: date, b: basalvalue.totalbasal } ); - comboareadata.push( { d: date, b: 0 } ); + tempbasalareadata.push({ d: date, b: 0 }); + basalareadata.push({ d: date, b: basalvalue.totalbasal }); + comboareadata.push({ d: date, b: 0 }); } } lastbasal = basalvalue; @@ -419,8 +554,8 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) // Draw IOB from devicestatuses if available if (iobStatusAvailable) { - var lastdate = 0; - var previousdate = 0; + lastdate = 0; + previousdate = 0; var iobArray = client.plugins('iob').IOBDeviceStatusesInTimeRange(datastorage.devicestatus, from.valueOf(), to.valueOf()); _.each(iobArray, function drawCob (point) { if (previousdate !== 0 && point.mills - previousdate > times.mins(15).msecs) { @@ -444,24 +579,24 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) .attr('stroke', 'blue') .attr('opacity', '0.5') .attr('fill-opacity', '0.1') - .attr('points',iobpolyline); - } + .attr('points', iobpolyline); + } if (options.cob) { context.append('polyline') .attr('stroke', 'red') .attr('opacity', '0.5') .attr('fill-opacity', '0.1') - .attr('points',cobpolyline); + .attr('points', cobpolyline); } if (options.basal) { var toTempBasal = profile.getTempBasal(to.format('x')); - linedata.push( { d: to.format('x'), b: toTempBasal.totalbasal } ); - notemplinedata.push( { d: to.format('x'), b: toTempBasal.basal } ); - basalareadata.push( { d: to.format('x'), b: toTempBasal.basal } ); - tempbasalareadata.push( { d: to.format('x'), b: toTempBasal.totalbasal } ); - comboareadata.push( { d: to.format('x'), b: toTempBasal.totalbasal } ); + linedata.push({ d: to.format('x'), b: toTempBasal.totalbasal }); + notemplinedata.push({ d: to.format('x'), b: toTempBasal.basal }); + basalareadata.push({ d: to.format('x'), b: toTempBasal.basal }); + tempbasalareadata.push({ d: to.format('x'), b: toTempBasal.totalbasal }); + comboareadata.push({ d: to.format('x'), b: toTempBasal.totalbasal }); var basalMax = d3.max(linedata, function(d) { return d.b; }); basalMax = Math.max(basalMax, d3.max(basalareadata, function(d) { return d.b; })); @@ -470,13 +605,13 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) yScaleBasals.domain([basalMax, 0]); - var valueline = d3.svg.line() - .interpolate('step-after') + var valueline = d3.line() + .curve(d3.curveStepAfter) .x(function(d) { return xScale2(d.d) + padding.left; }) .y(function(d) { return yScaleBasals(d.b) + padding.top; }); - var area = d3.svg.area() - .interpolate('step-after') + var area = d3.area() + .curve(d3.curveStepAfter) .x(function(d) { return xScale2(d.d) + padding.left; }) .y0(yScaleBasals(0) + padding.top) .y1(function(d) { return yScaleBasals(d.b) + padding.top; }); @@ -524,7 +659,7 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) .attr('stroke-width', 1) .attr('d', area); - datastorage.tempbasalTreatments.forEach(function (t) { + datastorage.tempbasalTreatments.forEach(function(t) { // only if basal and focus interval overlap and there is a chance to fit if (t.mills < to.format('x') && t.mills + times.mins(t.duration).msecs > from.format('x')) { var text = g.append('text') @@ -534,9 +669,9 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) .attr('fill', '#0099ff') .attr('text-anchor', 'middle') .attr('dy', '.35em') - .attr('x', xScale2((Math.max(t.mills, from.format('x')) + Math.min(t.mills + times.mins(t.duration).msecs, to.format('x')))/2) + padding.left) + .attr('x', xScale2((Math.max(t.mills, from.format('x')) + Math.min(t.mills + times.mins(t.duration).msecs, to.format('x'))) / 2) + padding.left) .attr('y', yScaleBasals(0) - 10 + padding.top) -// .text((t.percent ? (t.percent > 0 ? '+' : '') + t.percent + '%' : '') + (t.absolute ? Number(t.absolute).toFixed(2) + 'U' : '')); + // .text((t.percent ? (t.percent > 0 ? '+' : '') + t.percent + '%' : '') + (t.absolute ? Number(t.absolute).toFixed(2) + 'U' : '')); // better hide if not fit if (text.node().getBBox().width > xScale2(t.mills + times.mins(t.duration).msecs) - xScale2(t.mills)) { text.attr('display', 'none'); @@ -545,8 +680,7 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) }); } - - data.treatments.forEach(function (treatment) { + data.treatments.forEach(function(treatment) { // Calculate bolus stats if (treatment.insulin) { bolusInsulin += treatment.insulin; @@ -565,10 +699,10 @@ daytoday.report = function report_daytoday(datastorage,sorteddaystoshow,options) var drawpointer = false; if (treatment.boluscalc && treatment.boluscalc.foods && treatment.boluscalc.foods.length > 0 && options.food) { var foods = treatment.boluscalc.foods; - for (var fi=0; fi
' + translate('Total basal insulin:') + '' + totalBasalInsulin.toFixed(1) + 'U
' + translate('Total daily insulin:') + '' + totalDailyInsulin.toFixed(1) + 'U
' + translate('Total carbs') + ':' + data.dailyCarbs + ' g
' + translate('Total protein') + ':' + data.dailyProtein + ' g
' + translate('Total fat') + ':' + data.dailyFat + ' g
' + - '' + - '' + - '' + - '' + - '
' + - '
' + - '
' + - '
' + - '* ' + translate('This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from:') + - 'Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.' + '

' + - translate('Time in fluctuation and Time in rapid fluctuation measure the % of time during the examined period, during which the blood glucose has been changing relatively fast or rapidly. Lower values are better.') + '

' + - translate('Mean Total Daily Change is a sum of the absolute value of all glucose excursions for the examined period, divided by the number of days. Lower is better.')+ '

' + - translate('Mean Hourly Change is a sum of the absolute value of all glucose excursions for the examined period, divided by the number of hours in the period. Lower is better.')+ '

' + - translate('Out of Range RMS is calculated by squaring the distance out of range for all glucose readings for the examined period, summing them, dividing by the count and taking the square root. This metric is similar to in-range percentage but weights readings far out of range higher. Lower values are better.')+ '

' + - translate('GVI and PGS are measures developed by Dexcom, detailed here.')+ - '


' + - translate('Filter by hours') + ':' + - '
' + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - '10' + - '11' + - '12' + - '13' + - '14' + - '15' + - '16' + - '17' + - '18' + - '19' + - '20' + - '21' + - '22' + - '23' - - ; - return ret; +glucosedistribution.html = function html (client) { + var translate = client.translate; + var ret = + '

' + + translate('Glucose distribution') + + ' (' + + ' ' + + ' )' + + '

' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '* ' + translate('This is only a rough estimation that can be very inaccurate and does not replace actual blood testing. The formula used is taken from:') + + 'Nathan, David M., et al. "Translating the A1C assay into estimated average glucose values." Diabetes care 31.8 (2008): 1473-1478.' + '

' + + translate('Time in fluctuation and Time in rapid fluctuation measure the % of time during the examined period, during which the blood glucose has been changing relatively fast or rapidly. Lower values are better.') + '

' + + translate('Mean Total Daily Change is a sum of the absolute value of all glucose excursions for the examined period, divided by the number of days. Lower is better.') + '

' + + translate('Mean Hourly Change is a sum of the absolute value of all glucose excursions for the examined period, divided by the number of hours in the period. Lower is better.') + '

' + + translate('Out of Range RMS is calculated by squaring the distance out of range for all glucose readings for the examined period, summing them, dividing by the count and taking the square root. This metric is similar to in-range percentage but weights readings far out of range higher. Lower values are better.') + '

' + + translate('GVI (Glycemic Variability Index) and PGS (Patient Glycemic Status) are measures developed by Dexcom, detailed can be found here.') + + '


' + + translate('Filter by hours') + ':' + + '
' + + '0' + + '1' + + '2' + + '3' + + '4' + + '5' + + '6' + + '7' + + '8' + + '9' + + '10' + + '11' + + '12' + + '13' + + '14' + + '15' + + '16' + + '17' + + '18' + + '19' + + '20' + + '21' + + '22' + + '23'; + return ret; }; glucosedistribution.css = - '#glucosedistribution-overviewchart {' + - ' width: 2.4in;' + - ' height: 2.4in;' + - '}' + - '#glucosedistribution-placeholder .tdborder {' + - ' width:80px;' + - ' border: 1px #ccc solid;' + - ' margin: 0;' + - ' padding: 1px;' + - ' text-align:center;' + - '}'; - - - -glucosedistribution.report = function report_glucosedistribution(datastorage, sorteddaystoshow, options) { - var Nightscout = window.Nightscout; - var client = Nightscout.client; - var translate = client.translate; - var displayUnits = Nightscout.client.settings.units; - - var ss = require('simple-statistics'); - - var colors = ['#f88', '#8f8', '#ff8']; - var tablecolors = { - Low: '#f88', - Normal: '#8f8', - High: '#ff8' - }; - - var enabledHours = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]; - - var report = $('#glucosedistribution-report'); - report.empty(); - - var stability = $('#glucosedistribution-stability'); - stability.empty(); - - var stats = []; - var table = $(''); - var thead = $(''); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - $('').appendTo(thead); - thead.appendTo(table); - - var data = datastorage.allstatsrecords; - var days = datastorage.alldays; - - $('#glucosedistribution-days').text(days + ' ' + translate('days total')); - - for (var i = 0; i < 23; i++) { - $('#glucosedistribution-' + i).unbind('click').click(onClick); - enabledHours[i] = $('#glucosedistribution-' + i).is(':checked'); + '#glucosedistribution-overviewchart {' + + ' width: 2.4in;' + + ' height: 2.4in;' + + '}' + + '#glucosedistribution-placeholder .tdborder {' + + ' width:80px;' + + ' border: 1px #ccc solid;' + + ' margin: 0;' + + ' padding: 1px;' + + ' text-align:center;' + + '}'; + +glucosedistribution.report = function report_glucosedistribution (datastorage, sorteddaystoshow, options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var displayUnits = Nightscout.client.settings.units; + + var ss = require('simple-statistics'); + + var colors = ['#f88', '#8f8', '#ff8']; + var tablecolors = { + Low: '#f88' + , Normal: '#8f8' + , High: '#ff8' + }; + + var enabledHours = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]; + + var report = $('#glucosedistribution-report'); + report.empty(); + + var stability = $('#glucosedistribution-stability'); + stability.empty(); + + var stats = []; + var table = $('
' + translate('Range') + '' + translate('% of Readings') + '' + translate('# of Readings') + '' + translate('Average') + '' + translate('Median') + '' + translate('Standard Deviation') + '' + translate('A1c estimation*') + '
'); + var thead = $(''); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + $('').appendTo(thead); + thead.appendTo(table); + + var data = datastorage.allstatsrecords; + var days = datastorage.alldays; + + $('#glucosedistribution-days').text(days + ' ' + translate('days total')); + + for (var i = 0; i < 24; i++) { + $('#glucosedistribution-' + i).unbind('click').click(onClick); + enabledHours[i] = $('#glucosedistribution-' + i).is(':checked'); + } + + var result = {}; + + // Filter data for noise + // data cleaning pass 0 - remove duplicates and non-sgv entries, sort + var seen = []; + data = data.filter(function(item) { + if (!item.sgv || !item.bgValue || !item.displayTime || item.bgValue < 39) { + console.log(item); + return false; } + return seen.includes(item.displayTime) ? false : (seen[item.displayTime] = true); + }); - //console.log(enabledHours); + data.sort(function(a, b) { + return a.displayTime.getTime() - b.displayTime.getTime(); + }); - var result = {}; + var glucose_data = [data[0]]; - // Filter data for noise - - var glucose_data = [data[0]]; + if (data.length === 0) { + $('#glucosedistribution-days').text(translate('Result is empty')); + return; + } - // data cleaning pass 0 - remove duplicates and sort - - var seen = {}; - data = data.filter(function(item) { - return seen.hasOwnProperty(item.displayTime) ? false : (seen[item.displayTime] = true); - }); + // data cleaning pass 1 - add interpolated missing points + for (i = 0; i <= data.length - 2; i++) { + var entry = data[i]; + var nextEntry = data[i + 1]; - data.sort(function(a,b){ - return a.displayTime.getTime()-b.displayTime.getTime(); - }); + var timeDelta = nextEntry.displayTime.getTime() - entry.displayTime.getTime(); - // data cleaning pass 1 - add interpolated missing points + if (timeDelta < 9 * 60 * 1000 || timeDelta > 25 * 60 * 1000) { + glucose_data.push(entry); + continue; + } - for (var i = 0; i < data.length - 2; i++) { + var missingRecords = Math.floor(timeDelta / (5 * 60 * 990)) - 1; - var entry = data[i]; - var nextEntry = data[i + 1]; + var timePatch = Math.floor(timeDelta / (missingRecords + 1)); + var bgDelta = (nextEntry.bgValue - entry.bgValue) / (missingRecords + 1); - var timeDelta = nextEntry.displayTime.getTime() - entry.displayTime.getTime(); + glucose_data.push(entry); - if (timeDelta < 9 * 60 * 1000 ||  timeDelta > 25 * 60 * 1000) { - glucose_data.push(entry); - continue; - } + for (var j = 1; j <= missingRecords; j++) { + var bg = Math.floor(entry.bgValue + bgDelta * j); + var t = new Date(entry.displayTime.getTime() + j * timePatch); + var newEntry = { + sgv: displayUnits === 'mmol' ? bg / consts.MMOL_TO_MGDL : bg + , bgValue: bg + , displayTime: t + }; + glucose_data.push(newEntry); + } + } + // Need to add the last record, after interpolating between points + glucose_data.push(data[data.length - 1]); - var missingRecords = Math.floor(timeDelta / (5 * 60 * 990)) -1; + // data cleaning pass 2 - replace single jumpy measures with interpolated values + var glucose_data2 = [glucose_data[0]]; + var prevEntry = glucose_data[0]; - var timePatch = Math.floor(timeDelta / (missingRecords + 1)); - var bgDelta = (nextEntry.bgValue - entry.bgValue) / (missingRecords + 1); + const maxGap = (5 * 60 * 1000) + 10000; - glucose_data.push(entry); + for (i = 1; i <= glucose_data.length - 2; i++) { + let entry = glucose_data[i]; + let nextEntry = glucose_data[i + 1]; - for (var j = 1; j <= missingRecords; j++) { - - var bg = Math.floor(entry.bgValue + bgDelta * j); - var t = new Date(entry.displayTime.getTime() + j * timePatch); - var newEntry = { - bgValue: bg, - displayTime: t - }; - glucose_data.push(newEntry); - } + let timeDelta = nextEntry.displayTime.getTime() - entry.displayTime.getTime(); + let timeDelta2 = entry.displayTime.getTime() - prevEntry.displayTime.getTime(); + if (timeDelta > maxGap || timeDelta2 > maxGap) { + glucose_data2.push(entry); + prevEntry = entry; + continue; } - // data cleaning pass 2 - replace single jumpy measures with interpolated values - - var glucose_data2 = [glucose_data[0]]; - - var prevEntry = glucose_data[0]; - - const maxGap = (5 * 60 * 1000) + 10000; - - for (var i = 1; i < glucose_data.length-2; i++) { - -// var prevEntry = glucose_data[i-1]; - var entry = glucose_data[i]; - var nextEntry = glucose_data[i+1]; - - var timeDelta = nextEntry.displayTime.getTime() - entry.displayTime.getTime(); - var timeDelta2 = entry.displayTime.getTime() - prevEntry.displayTime.getTime(); - - if (timeDelta > maxGap || timeDelta2 > maxGap ) { - glucose_data2.push(entry); - prevEntry = entry; - continue; - } - - var delta1 = entry.bgValue - prevEntry.bgValue; - var delta2 = nextEntry.bgValue - entry.bgValue; - - if (delta1 <= 8 && delta2 <= 8) { - glucose_data2.push(entry); - prevEntry = entry; - continue; - } - - - if ((delta1 > 0 && delta2 <0) || (delta1 < 0 && delta2 > 0)) { - var d = (nextEntry.bgValue - prevEntry.bgValue) / 2; - var newEntry = { - bgValue: prevEntry.bgValue + d, - displayTime: entry.displayTime - }; - glucose_data2.push(newEntry); - prevEntry = newEntry; - continue; - - } - - glucose_data2.push(entry); - prevEntry = entry; - } - - glucose_data = data = glucose_data2.filter(function(r) { - return enabledHours[new Date(r.displayTime).getHours()] - }); - - glucose_data.sort(function(a,b){ - return a.displayTime.getTime()-b.displayTime.getTime(); - }); - - var timeTotal = 0; - - for (var i = 1; i < glucose_data.length-2; i++) { - var entry = glucose_data[i]; - var nextEntry = glucose_data[i+1]; - var timeDelta = nextEntry.displayTime.getTime() - entry.displayTime.getTime(); - if (timeDelta < maxGap) { - timeTotal += timeDelta; - } - } - - var daysTotal = timeTotal / (1000*60*60*24); - - ['Low', 'Normal', 'High'].forEach(function(range) { - result[range] = {}; - var r = result[range]; - r.rangeRecords = glucose_data.filter(function(r) { - if (range === 'Low') { - return r.sgv > 0 && r.sgv < options.targetLow; - } else if (range === 'Normal') { - return r.sgv >= options.targetLow && r.sgv < options.targetHigh; - } else { - return r.sgv >= options.targetHigh; - } - }); - stats.push(r.rangeRecords.length); - r.rangeRecords.sort(function(a, b) { - return a.sgv - b.sgv; - }); - r.localBgs = r.rangeRecords.map(function(r) { - return r.sgv; - }).filter(function(bg) { - return !!bg; - }); - r.midpoint = Math.floor(r.rangeRecords.length / 2); - r.readingspct = (100 * r.rangeRecords.length / data.length).toFixed(1); - if (r.rangeRecords.length > 0) { - r.mean = Math.floor(10 * ss.mean(r.localBgs)) / 10; - r.median = r.rangeRecords[r.midpoint].sgv; - r.stddev = Math.floor(ss.standard_deviation(r.localBgs) * 10) / 10; - } - }); - - // make sure we have total 100% - result.Normal.readingspct = (100 - result.Low.readingspct - result.High.readingspct).toFixed(1); - - ['Low', 'Normal', 'High'].forEach(function(range) { - var tr = $(''); - var r = result[range]; - - var rangeExp = ''; + var delta1 = entry.bgValue - prevEntry.bgValue; + var delta2 = nextEntry.bgValue - entry.bgValue; - if (range == 'Low') { - rangeExp = ' (<' + options.targetLow + ')'; - } - if (range == 'High') { - rangeExp = ' (>=' + options.targetHigh + ')'; - } + if (delta1 <= 8 && delta2 <= 8) { + glucose_data2.push(entry); + prevEntry = entry; + continue; + } - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - if (r.rangeRecords.length > 0) { - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - } else { - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - } + if ((delta1 > 0 && delta2 < 0) || (delta1 < 0 && delta2 > 0)) { + const d = (nextEntry.bgValue - prevEntry.bgValue) / 2; + const interpolatedValue = prevEntry.bgValue + d; + + let newEntry = { + sgv: displayUnits === 'mmol' ? interpolatedValue / consts.MMOL_TO_MGDL : interpolatedValue + , bgValue: interpolatedValue + , displayTime: entry.displayTime + }; + glucose_data2.push(newEntry); + prevEntry = newEntry; + continue; + } - table.append(tr); + glucose_data2.push(entry); + prevEntry = entry; + } + // Need to add the last record, after interpolating between points + glucose_data2.push(glucose_data[glucose_data.length - 1]); + + glucose_data = data = glucose_data2.filter(function(r) { + return enabledHours[new Date(r.displayTime).getHours()] + }); + + glucose_data.sort(function(a, b) { + return a.displayTime.getTime() - b.displayTime.getTime(); + }); + + var timeTotal = 0; + for (i = 1; i <= glucose_data.length - 2; i++) { + let entry = glucose_data[i]; + let nextEntry = glucose_data[i + 1]; + let timeDelta = nextEntry.displayTime.getTime() - entry.displayTime.getTime(); + if (timeDelta < maxGap) { + timeTotal += timeDelta; + } + } + + var daysTotal = timeTotal / (1000 * 60 * 60 * 24); + + ['Low', 'Normal', 'High'].forEach(function(range) { + result[range] = {}; + var r = result[range]; + r.rangeRecords = glucose_data.filter(function(r) { + if (range === 'Low') { + return r.sgv > 0 && r.sgv < options.targetLow; + } else if (range === 'Normal') { + return r.sgv >= options.targetLow && r.sgv < options.targetHigh; + } else { + return r.sgv >= options.targetHigh; + } }); - - var tr = $(''); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - if (glucose_data.length > 0) { - var localBgs = glucose_data.map(function(r) { - return r.sgv; - }).filter(function(bg) { - return !!bg; - }); - var mgDlBgs = glucose_data.map(function(r) { - return r.bgValue; - }).filter(function(bg) { - return !!bg; - }); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - } else { - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); + stats.push(r.rangeRecords.length); + r.rangeRecords.sort(function(a, b) { + return a.sgv - b.sgv; + }); + r.localBgs = r.rangeRecords.map(function(r) { + return r.sgv; + }).filter(function(bg) { + return !!bg; + }); + r.midpoint = Math.floor(r.rangeRecords.length / 2); + r.readingspct = (100 * r.rangeRecords.length / data.length).toFixed(1); + if (r.rangeRecords.length > 0) { + r.mean = Math.floor(10 * ss.mean(r.localBgs)) / 10; + r.median = r.rangeRecords[r.midpoint].sgv; + r.stddev = Math.floor(ss.standard_deviation(r.localBgs) * 10) / 10; } - table.append(tr); - report.append(table); - + }); - // Stability + // make sure we have total 100% + result.Normal.readingspct = (100 - result.Low.readingspct - result.High.readingspct).toFixed(1); - var t1 = 6; - var t2 = 11; - var t1count = 0; - var t2count = 0; - - var total = 0; - var events = 0; - - var GVITotal = 0; - var GVIIdeal = 0; - - var RMSTotal = 0; - - var usedRecords = 0; - var glucoseTotal = 0; - var deltaTotal = 0; + ['Low', 'Normal', 'High'].forEach(function(range) { + var tr = $(''); + var r = result[range]; - for (var i = 0; i < glucose_data.length - 2; i++) { + var rangeExp = ''; + if (range == 'Low') { + rangeExp = ' (<' + options.targetLow + ')'; + } + if (range == 'High') { + rangeExp = ' (>=' + options.targetHigh + ')'; + } - var entry = glucose_data[i]; - var nextEntry = glucose_data[i + 1]; + var rangeLabel = range; + if (rangeLabel == 'Normal') rangeLabel = 'In Range'; + + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + if (r.rangeRecords.length > 0) { + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + } else { + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + } - var timeDelta = nextEntry.displayTime.getTime() - entry.displayTime.getTime(); + table.append(tr); + }); + + var tr = $(''); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + if (glucose_data.length > 0) { + var localBgs = glucose_data.map(function(r) { + return r.sgv; + }).filter(function(bg) { + return !!bg; + }); + var mgDlBgs = glucose_data.map(function(r) { + return r.bgValue; + }).filter(function(bg) { + return !!bg; + }); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + } else { + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + $('').appendTo(tr); + } + table.append(tr); + report.append(table); + + // Stability + var t1 = 6; + var t2 = 11; + var t1count = 0; + var t2count = 0; + + var events = 0; + + var GVITotal = 0; + var GVIIdeal = 0; + var GVIIdeal_Time = 0; + + var RMSTotal = 0; + + var usedRecords = 0; + var glucoseTotal = 0; + var deltaTotal = 0; + + for (i = 0; i <= glucose_data.length - 2; i++) { + const entry = glucose_data[i]; + const nextEntry = glucose_data[i + 1]; + const timeDelta = nextEntry.displayTime.getTime() - entry.displayTime.getTime(); + + // Use maxGap constant + if (timeDelta == 0 || timeDelta > maxGap) { // 6 * 60 * 1000) { + // console.log("Record skipped"); + continue; + } - if (timeDelta > 6 * 60 * 1000) { -// console.log("Record skipped"); - continue; - } - - usedRecords += 1; + usedRecords += 1; + events += 1; + + var delta = Math.abs(nextEntry.bgValue - entry.bgValue); + deltaTotal += delta; + + if (delta > 0) { // avoid divide by 0 error + // Are we rising at faster than 5mg/DL/5minutes + if ((delta / timeDelta) >= (t1 / (1000 * 60 * 5))) { + t1count += 1; + } + // Are we rising at faster than 10mg/DL/5minutes + if ((delta / timeDelta) >= (t2 / (1000 * 60 * 5))) { + t2count += 1; + } + } - var delta = Math.abs(nextEntry.bgValue - entry.bgValue); - - deltaTotal += delta; + // Calculate the distance travelled for this time step + GVITotal += Math.sqrt(Math.pow(timeDelta / (1000 * 60), 2) + Math.pow(delta, 2)); - total += delta; - events += 1; + // Keep track of the number of minutes in this timestep + GVIIdeal_Time += timeDelta / (1000 * 60); + glucoseTotal += entry.bgValue; - if (delta >= t1) { - t1count += 1; - } - - if (delta >= t2) { - t2count += 1; - } - - GVITotal += Math.sqrt(25 + Math.pow(delta, 2)); - glucoseTotal += entry.bgValue; + if (entry.bgValue < options.targetLow) { + RMSTotal += Math.pow(options.targetLow - entry.bgValue, 2); + } + if (entry.bgValue > options.targetHigh) { + RMSTotal += Math.pow(entry.bgValue - options.targetHigh, 2); + } + } - if (entry.bgValue < options.targetLow) { - RMSTotal += Math.pow(options.targetLow - entry.bgValue, 2); - } - if (entry.bgValue > options.targetHigh) { - RMSTotal += Math.pow(entry.bgValue - options.targetHigh, 2); - } + // Difference between first and last reading + var GVIDelta = Math.floor(glucose_data[0].bgValue - glucose_data[glucose_data.length - 1].bgValue); - } - - var GVIDelta = Math.floor(glucose_data[0].bgValue,glucose_data[glucose_data.length-1].bgValue); - - GVIIdeal = Math.sqrt(Math.pow(usedRecords*5,2) + Math.pow(GVIDelta,2)); - - var GVI = Math.round(GVITotal / GVIIdeal * 100) / 100; - - console.log('GVI',GVI,'GVIIdeal',GVIIdeal,'GVITotal',GVITotal); - - var glucoseMean = Math.floor(glucoseTotal / usedRecords); + // Delta for total time considered against total period rise + GVIIdeal = Math.sqrt(Math.pow(GVIIdeal_Time, 2) + Math.pow(GVIDelta, 2)); - var tirMultiplier = result.Normal.readingspct / 100.0; + var GVI = Math.round(GVITotal / GVIIdeal * 100) / 100; + console.log('GVI', GVI, 'GVIIdeal', GVIIdeal, 'GVITotal', GVITotal, 'GVIIdeal_Time', GVIIdeal_Time); - var PGS = Math.round(GVI * glucoseMean * (1-tirMultiplier) * 100) / 100; + var glucoseMean = Math.floor(glucoseTotal / usedRecords); + var tirMultiplier = result.Normal.readingspct / 100.0; + var PGS = Math.round(GVI * glucoseMean * (1 - tirMultiplier) * 100) / 100; + console.log('glucoseMean', glucoseMean, 'tirMultiplier', tirMultiplier, 'PGS', PGS); - console.log('glucoseMean', glucoseMean,'tirMultiplier',tirMultiplier, 'PGS',PGS); + var TDC = deltaTotal / daysTotal; + var TDCHourly = TDC / 24.0; - var TDC = deltaTotal / daysTotal; - - var TDCHourly = TDC / 24.0; + var RMS = Math.sqrt(RMSTotal / events); - var RMS = Math.sqrt(RMSTotal / events); + // console.log('TADC',TDC,'days',days); -// console.log('TADC',TDC,'days',days); + var timeInT1 = Math.round(100 * t1count / events).toFixed(1); + var timeInT2 = Math.round(100 * t2count / events).toFixed(1); - var timeInT1 = Math.round(100 * t1count / events).toFixed(1); - var timeInT2 = Math.round(100 * t2count / events).toFixed(1); + var unitString = ' mg/dl'; + if (displayUnits == 'mmol') { + TDC = TDC / consts.MMOL_TO_MGDL; + TDCHourly = TDCHourly / consts.MMOL_TO_MGDL; + unitString = ' mmol/L'; - var unitString = ' mg/dl'; + RMS = Math.sqrt(RMSTotal / events) / consts.MMOL_TO_MGDL; + } - if (displayUnits == 'mmol') { - TDC = TDC / 18.0; - TDCHourly = TDCHourly / 18.0; - unitString = ' mmol/L'; - - RMS = Math.sqrt(RMSTotal / events) / 18; - } - - TDC = Math.round(TDC * 100) / 100; - TDCHourly = Math.round(TDCHourly * 100) / 100; + TDC = Math.round(TDC * 100) / 100; + TDCHourly = Math.round(TDCHourly * 100) / 100; - var stabilitytable = $('
' + translate('Range') + '' + translate('% of Readings') + '' + translate('# of Readings') + '' + translate('Average') + '' + translate('Median') + '' + translate('Standard Deviation') + '' + translate('A1c estimation*') + '
' + translate(range) + rangeExp + ': ' + r.readingspct + '%' + r.rangeRecords.length + '' + r.mean.toFixed(1) + '' + r.median.toFixed(1) + '' + r.stddev.toFixed(1) + ' N/AN/AN/A
' + translate('Overall') + ': ' + glucose_data.length + '' + (Math.round(10 * ss.mean(localBgs)) / 10).toFixed(1) + '' + (Math.round(10 * ss.quantile(localBgs, 0.5)) / 10).toFixed(1) + '' + (Math.round(ss.standard_deviation(localBgs) * 10) / 10).toFixed(1) + '
' + (Math.round(10 * (ss.mean(mgDlBgs) + 46.7) / 28.7) / 10).toFixed(1) + '%DCCT | ' + Math.round(((ss.mean(mgDlBgs) + 46.7) / 28.7 - 2.15) * 10.929) + 'IFCC
N/AN/AN/AN/A
' + translate(rangeLabel) + rangeExp + ': ' + r.readingspct + '%' + r.rangeRecords.length + '' + r.mean.toFixed(1) + '' + r.median.toFixed(1) + '' + r.stddev.toFixed(1) + ' N/AN/AN/A
' + translate('Overall') + ': ' + glucose_data.length + '' + (Math.round(10 * ss.mean(localBgs)) / 10).toFixed(1) + '' + (Math.round(10 * ss.quantile(localBgs, 0.5)) / 10).toFixed(1) + '' + (Math.round(ss.standard_deviation(localBgs) * 10) / 10).toFixed(1) + '
' + (Math.round(10 * (ss.mean(mgDlBgs) + 46.7) / 28.7) / 10).toFixed(1) + '%DCCT | ' + Math.round(((ss.mean(mgDlBgs) + 46.7) / 28.7 - 2.15) * 10.929) + 'IFCC
N/AN/AN/AN/A
'); + var stabilitytable = $('
'); - var t1exp = '>5 mg/dl/5m'; - var t2exp = '>10 mg/dl/5m'; + var t1exp = '>5 mg/dl/5m'; + var t2exp = '>10 mg/dl/5m'; + if (displayUnits == 'mmol') { + t1exp = '>0.27 mmol/l/5m'; + t2exp = '>0.55 mmol/l/5m'; + } - if (displayUnits == 'mmol') { - t1exp = '>0.27 mmol/l/5m'; - t2exp = '>0.55 mmol/l/5m'; - } + $('').appendTo(stabilitytable); + $('').appendTo(stabilitytable); - $('').appendTo(stabilitytable); - $('').appendTo(stabilitytable); - - $('').appendTo(stabilitytable); - $('').appendTo(stabilitytable); - -// $('').appendTo(stabilitytable); -// $('').appendTo(stabilitytable); - - stabilitytable.appendTo(stability); - - setTimeout(function() { - $.plot( - '#glucosedistribution-overviewchart', - stats, { - series: { - pie: { - show: true - } - }, - colors: colors - } - ); - }); + $('').appendTo(stabilitytable); + $('').appendTo(stabilitytable); - function onClick() { - report_glucosedistribution(datastorage, sorteddaystoshow, options); - } + $('').appendTo(stabilitytable); + $('').appendTo(stabilitytable); + stabilitytable.appendTo(stability); + setTimeout(function() { + $.plot( + '#glucosedistribution-overviewchart' + , stats, { + series: { + pie: { + show: true + } + } + , colors: colors + } + ); + }); + + function onClick () { + report_glucosedistribution(datastorage, sorteddaystoshow, options); + } }; diff --git a/lib/report_plugins/hourlystats.js b/lib/report_plugins/hourlystats.js index 12a4412666b..84114209c96 100644 --- a/lib/report_plugins/hourlystats.js +++ b/lib/report_plugins/hourlystats.js @@ -8,34 +8,33 @@ var hourlystats = { , pluginType: 'report' }; -function init() { +function init () { return hourlystats; } module.exports = init; -hourlystats.html = function html(client) { +hourlystats.html = function html (client) { var translate = client.translate; var ret = - '

' + translate('Hourly stats') + '

' - + '
' - + '
' - ; + '

' + translate('Hourly stats') + '

' + + '
' + + '
'; return ret; }; hourlystats.css = - '#hourlystats-overviewchart {' - + ' width: 100%;' - + ' min-width: 6.5in;' - + ' height: 5in;' - + '}' - + '#hourlystats-placeholder td {' - + ' text-align:center;' - + '}'; - -hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, options) { -//console.log(window); + '#hourlystats-overviewchart {' + + ' width: 100%;' + + ' min-width: 6.5in;' + + ' height: 5in;' + + '}' + + '#hourlystats-placeholder td {' + + ' text-align:center;' + + '}'; + +hourlystats.report = function report_hourlystats (datastorage, sorteddaystoshow, options) { + //console.log(window); var ss = require('simple-statistics'); var Nightscout = window.Nightscout; var client = Nightscout.client; @@ -52,15 +51,15 @@ hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, pivotedByHour[i] = []; } - data = data.filter(function(o) { return !isNaN(o.sgv);}); - + data = data.filter(function(o) { return !isNaN(o.sgv); }); + data.forEach(function(record) { var d = new Date(record.displayTime); record.sgv = Number(record.sgv); pivotedByHour[d.getHours()].push(record); }); - + var table = $('
' + translate('Mean Total Daily Change') + '' + translate('Time in fluctuation') + '
(' + t1exp + ')
' + translate('Time in rapid fluctuation') + '
(' + t2exp + ')
' + TDC + unitString + '' + timeInT1 + '%' + timeInT2 + '%
' + translate('Mean Total Daily Change') + '' + translate('Time in fluctuation') + '
(' + t1exp + ')
' + translate('Time in rapid fluctuation') + '
(' + t2exp + ')
' + TDC + unitString + '' + timeInT1 + '%' + timeInT2 + '%
' + translate('Mean Hourly Change') + 'GVIPGS
' + TDCHourly + unitString + '' + GVI + '' + PGS + '
Out of Range RMS
' + Math.round(RMS * 100) / 100 + unitString + '
' + translate('Mean Hourly Change') + 'GVIPGS
' + TDCHourly + unitString + '' + GVI + '' + PGS + '
Out of Range RMS
' + Math.round(RMS * 100) / 100 + unitString + '
'); var thead = $(''); $('').appendTo(thead); @@ -74,50 +73,53 @@ hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, $('').appendTo(thead); thead.appendTo(table); - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23].forEach(function (hour) { + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23].forEach(function(hour) { var tr = $(''); var display = new Date(0, 0, 1, hour, 0, 0, 0).toLocaleTimeString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3'); - var avg = Math.floor(pivotedByHour[hour].map(function (r) { - return r.sgv; - }).reduce(function (o, v) { - return o + v; - }, 0) / pivotedByHour[hour].length); + var avg = Math.floor(pivotedByHour[hour].map(function(r) { + return r.sgv; + }).reduce(function(o, v) { + return o + v; + }, 0) / pivotedByHour[hour].length); var d = new Date(times.hours(hour).msecs); - var dev = ss.standard_deviation(pivotedByHour[hour].map(function (r) { + var dev = ss.standard_deviation(pivotedByHour[hour].map(function(r) { return r.sgv; })); stats.push([ - new Date(d), - ss.quantile(pivotedByHour[hour].map(function (r) { + new Date(d) + , ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; - }), 0.25), - ss.quantile(pivotedByHour[hour].map(function (r) { + }), 0.25) + , ss.quantile(pivotedByHour[hour].map(function(r) { return r.sgv; - }), 0.75), - avg - dev, - avg + dev + }), 0.75) + , avg - dev + , avg + dev ]); var tmp; $('').appendTo(tr); $('').appendTo(tr); $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); - $('').appendTo(tr); + $('').appendTo(tr); + // eslint-disable-next-line no-cond-assign + $('').appendTo(tr); + // eslint-disable-next-line no-cond-assign + $('').appendTo(tr); + // eslint-disable-next-line no-cond-assign + $('').appendTo(tr); + $('').appendTo(tr); $('').appendTo(tr); table.append(tr); }); @@ -126,28 +128,27 @@ hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, report.append(table); $.plot( - '#hourlystats-overviewchart', - [{ - data: stats, - candle: true - }], - { + '#hourlystats-overviewchart' + , [{ + data: stats + , candle: true + }], { series: { - candle: true, - lines: false //Somehow it draws lines if you dont disable this. Should investigate and fix this ;) - }, - xaxis: { - mode: 'time', - timeFormat: '%h:00', - min: 0, - max: times.hours(24).msecs - times.secs(1).msecs - }, - yaxis: { - min: 0, - max: options.units === 'mmol' ? 22 : 400, - show: true - }, - grid: { + candle: true + , lines: false //Somehow it draws lines if you dont disable this. Should investigate and fix this ;) + } + , xaxis: { + mode: 'time' + , timeFormat: '%h:00' + , min: 0 + , max: times.hours(24).msecs - times.secs(1).msecs + } + , yaxis: { + min: 0 + , max: options.units === 'mmol' ? 22 : 400 + , show: true + } + , grid: { show: true } } @@ -161,7 +162,7 @@ hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, var days = 0; table = $('
' + translate('Time') + '' + translate('Standard Deviation') + '
' + display + '' + pivotedByHour[hour].length + ' (' + Math.floor(100 * pivotedByHour[hour].length / data.length) + '%)' + avg + '' + Math.min.apply(Math, pivotedByHour[hour].map(function (r) { - return r.sgv; - })) + '' + ((tmp = ss.quantile(pivotedByHour[hour].map(function (r) { - return r.sgv; - }), 0.25)) ? tmp.toFixed(1) : 0 ) + '' + ((tmp = ss.quantile(pivotedByHour[hour].map(function (r) { - return r.sgv; - }), 0.5)) ? tmp.toFixed(1) : 0 ) + '' + ((tmp = ss.quantile(pivotedByHour[hour].map(function (r) { - return r.sgv; - }), 0.75)) ? tmp.toFixed(1) : 0 ) + '' + Math.max.apply(Math, pivotedByHour[hour].map(function (r) { - return r.sgv; - })) + '' + Math.min.apply(Math, pivotedByHour[hour].map(function(r) { + return r.sgv; + })) + '' + ((tmp = ss.quantile(pivotedByHour[hour].map(function(r) { + return r.sgv; + }), 0.25)) ? tmp.toFixed(1) : 0) + '' + ((tmp = ss.quantile(pivotedByHour[hour].map(function(r) { + return r.sgv; + }), 0.5)) ? tmp.toFixed(1) : 0) + '' + ((tmp = ss.quantile(pivotedByHour[hour].map(function(r) { + return r.sgv; + }), 0.75)) ? tmp.toFixed(1) : 0) + '' + Math.max.apply(Math, pivotedByHour[hour].map(function(r) { + return r.sgv; + })) + '' + Math.floor(dev * 10) / 10 + '
'); thead = $(''); - ["", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23].forEach(function (hour) { + ["", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23].forEach(function(hour) { $('').appendTo(thead); totalPositive[hour] = 0; totalNegative[hour] = 0; @@ -171,7 +172,7 @@ hourlystats.report = function report_hourlystats(datastorage, sorteddaystoshow, }); thead.appendTo(table); - sorteddaystoshow.forEach(function (day) { + sorteddaystoshow.forEach(function(day) { if (datastorage[day].netBasalPositive) { days++; var tr = $(''); diff --git a/lib/report_plugins/index.js b/lib/report_plugins/index.js index 12cd024bdd2..94bc282395f 100644 --- a/lib/report_plugins/index.js +++ b/lib/report_plugins/index.js @@ -11,6 +11,7 @@ function init() { } , allPlugins = [ require('./daytoday')() + , require('./weektoweek')() , require('./dailystats')() , require('./glucosedistribution')() , require('./hourlystats')() @@ -19,6 +20,7 @@ function init() { , require('./calibrations')() , require('./treatments')() , require('./profiles')() + , require('./loopalyzer')() ]; consts.scaleYFromSettings = function scaleYFromSettings (client) { diff --git a/lib/report_plugins/loopalyzer.js b/lib/report_plugins/loopalyzer.js new file mode 100644 index 00000000000..2d53fa251a3 --- /dev/null +++ b/lib/report_plugins/loopalyzer.js @@ -0,0 +1,1272 @@ +'use strict'; + +//var _ = require('lodash'); +var moment = window.moment; +//var times = require('../times'); +//var d3 = (global && global.d3) || require('d3'); + +var loopalyzer = { + name: 'loopalyzer' + , label: 'Loopalyzer' + , pluginType: 'report' +}; + +function init () { + return loopalyzer; +} + +module.exports = init; + +var laDebug = false; // If we should print console.logs +var laVersion = '2019-02-02 v6'; +var risingInterpolationGap = 6; // How large a gap in COB/IOB graph is allowed to be to be interpolated if end value is larger than start +var fallingInterpolationGap = 24; // And if less than start +var interpolationRatio = 1.25; // But do allow rising interpolation if gap larger than interpolationGap and end value is less than 10% larger than start + +loopalyzer.html = function html (client) { + var translate = client.translate; + var ret = ''; + ret += '

Loopalyzer  

'; + ret += '' + translate('The primary purpose of Loopalyzer is to visualise how the Loop closed loop system performs. It may work with other setups as well, both closed and open loop, and non loop. However depending on which uploader you use, how frequent it is able to capture your data and upload, and how it is able to backfill missing data some graphs may have gaps or even be completely empty. Always ensure the graphs look reasonable. Best is to view one day at a time and scroll through a number of days first to see.'); + ret += '

' + translate('Loopalyzer includes a time shift feature. If you for example have breakfast at 07:00 one day and at 08:00 the day after your average blood glucose curve these two days will most likely look flattened and not show the actual response after a breakfast. Time shift will compute the average time these meals were eaten and then shift all data (carbs, insulin, basal etc.) during both days the corresponding time difference so that both meals align with the average meal start time. '); + ret += '
' + translate('In this example all data from first day is pushed 30 minutes forward in time and all data from second day 30 minutes backward in time so it appears as if you had had breakfast at 07:30 both days. This allows you to see your actual average blood glucose response from a meal.'); + ret += '

' + translate('Time shift highlights the period after the average meal start time in gray, for the duration of the DIA (Duration of Insulin Action). As all data points the entire day are shifted the curves outside the gray area may not be accurate.'); + ret += '

' + translate('Note that time shift is available only when viewing multiple days.'); + ret += '

'; + ret += translate('To see this report, press SHOW while in this view'); + ret += '
'; + ret += ''; + ret += ''; /* loopalyzer-button */ + ret += '
'; + ret += '
'; + ret += '
'; + ret += '
'; + ret += '
'; + ret += '
'; + ret += '
'; + ret += '
'; + ret += '
'; + return ret; +}; + +loopalyzer.css = + '#loopalyzer-charts, #loopalyzer-profiles { padding: 20px; } ' + + '#loopalyzer-basal, #loopalyzer-bg, #loopalyzer-tempbasal, #loopalyzer-iob, #loopalyzer-cob, #loopalyzer-profiles {' + + ' width: 100%;' + + ' height: 100%;' + + '}' + + '#loopalyzer-profiles-table table { margin: 0 10px; border-collapse: collapse; border: 0px; }' + + '#loopalyzer-profiles-table td { vertical-align: top; }' + + '#loopalyzer-profiles-table td table { margin: 0 10px; border-collapse: collapse; border: 0px; }' + + '#loopalyzer-profiles-table td caption { text-align: left; font-weight: bold; }' + + '#loopalyzer-profiles-table td th { background-color: #4CAF50; color: white; }' + + '#loopalyzer-profiles-table td td { text-align: right; vertical-align: top; padding: 0 1px; }' + + '#loopalyzer-profiles-table td td td { padding: 1px 8px; }'; + +loopalyzer.prepareHtml = function loopalyzerPrepareHtml () { + // $('#loopalyzer-charts').append($('
' + hour + '
')); +}; + +// loopalyzer.ss = require('simple-statistics'); + +// +// Functions to pull data from datastorage and prepare in bins +// +loopalyzer.getSGVs = function(datastorage, daysToShow) { + var data = datastorage.allstatsrecords; + var bins = loopalyzer.getEmptyBins(); + + // Loop thru the days to show, for each day find the matching SGVs and insert into the bins entry array + daysToShow.forEach(function(day) { + var entries = []; // Array with all SGVs for this day, we'll fill this and then insert into the bins later + for (let i = 0; i < 288; i++) entries.push(NaN); // Fill the array with NaNs so we have something in case we don't find an SGV + var fromDate = moment(day); + var toDate = moment(day); + fromDate.set({ 'hours': 0, 'minutes': 0, 'seconds': 0, 'milliseconds': 0 }); + toDate.set({ 'hours': 0, 'minutes': 5, 'seconds': 0, 'milliseconds': 0 }); // toDate is 5 mins ahead + for (let i = 0; i < 288; i++) { + var found = false; + data.some(function(record) { + var recDate = moment(record.displayTime); + if (!found && recDate.isAfter(fromDate) && recDate.isBefore(toDate)) { + entries[i] = record.sgv; + found = true; + } + return found; // Breaks .some loop if found is true + }) + fromDate.add(5, 'minutes'); + toDate.add(5, 'minutes'); + } + loopalyzer.addArrayToBins(bins, entries); + }); + return bins; +} + +loopalyzer.getBasals = function(datastorage, daysToShow, profile) { + var bins = loopalyzer.getEmptyBins(); + + daysToShow.forEach(function(day) { + var dayStart = moment(day).startOf('day'); + var dayEnd = moment(day).endOf('day'); + var basals = []; + for (var i = 0; i < 288; i++) basals.push(NaN); // Clear the basals by filling with NaNs + + var index = 0; + for (var dt = dayStart; dt < dayEnd; dt.add(5, 'minutes')) { + var basal = profile.getTempBasal(dt.toDate()); + if (basal) + basals[index++] = basal.basal; + } + if (laDebug) console.log('getBasals ' + day, basals); + loopalyzer.addArrayToBins(bins, basals); + }); + return bins; +} + +loopalyzer.getTempBasalDeltas = function(datastorage, daysToShow, profile) { + var bins = loopalyzer.getEmptyBins(); + + daysToShow.forEach(function(day) { + var dayStart = moment(day).startOf('day'); + var dayEnd = moment(day).endOf('day'); + var temps = []; + for (var i = 0; i < 288; i++) temps.push(NaN); // Clear the basals by filling with NaNs + + var index = 0; + for (var dt = dayStart; dt < dayEnd; dt.add(5, 'minutes')) { + var basal = profile.getTempBasal(dt.toDate()); + if (basal) + temps[index++] = basal.tempbasal - basal.basal; + } + if (laDebug) console.log('getTempBasalDeltas ' + day, temps); + loopalyzer.addArrayToBins(bins, temps); + }); + return bins; +} + +loopalyzer.getIOBs = function(datastorage, daysToShow, profile, client, treatments) { + var iobStatusAvailable = client.plugins('iob').isDeviceStatusAvailable(datastorage.devicestatus); + if (laDebug) console.log('getIOBs iobStatusAvailable=' + iobStatusAvailable); + + var bins = loopalyzer.getEmptyBins(); + + daysToShow.forEach(function(day) { + var dayStart = moment(day).startOf('day'); + var dayEnd = moment(day).endOf('day'); + var iobs = []; + if (iobStatusAvailable) { + // var dayStartMills = dayStart.milliseconds(); + for (var i = 0; i < 288; i++) iobs.push(NaN); // Clear the IOBs by filling with NaNs + var iobArray = client.plugins('iob').IOBDeviceStatusesInTimeRange(datastorage.devicestatus, dayStart.valueOf(), dayEnd.valueOf()); + if (laDebug) console.log('getIOBs iobArray', iobArray); + iobArray.forEach(function(entry) { + var index = Math.floor(moment(entry.mills).diff(dayStart, 'minutes') / 5); + iobs[index] = entry.iob; + }); + + if (daysToShow.length === 1) loopalyzer.fillNanWithTreatments(iobs, treatments); + + // Loop thru these entries and where no IOB has been found, interpolate between nearby to get a continuous array + var startIndex = 0 + , stopIndex = 0; + while (startIndex < iobs.length && isNaN(iobs[startIndex])) { + startIndex++; // Advance start to the first real number + } + if (startIndex < iobs.length) { + stopIndex = startIndex + 1; + while (stopIndex < iobs.length) { + while (stopIndex < iobs.length && isNaN(iobs[stopIndex])) { + stopIndex++; // Advance stop to the first real number after start + } + if (stopIndex < iobs.length) { + // Now we have real numbers at start and stop and NaNs in between + // Compute the y=k*x+m = (y2-y1)/(x2-x1)*x+y1 + // Only interpolate on decreasing or steady, or on increasing if the gap is less than interpolationGap + // if (stopIndex-startIndex= 0 && isNaN(array[start])) {} + // eslint-disable-next-line no-empty + while (stop++ < array.length && isNaN(array[stop])) {} + // var gap = stop - start; + // if (isNaN(array[start]) || isNaN(array[stop]) || gap > interpolationGap || (gap < interpolationGap && array[start]= interpolationGap || array[start]==0)) ) { + var interpolate = (isNaN(array[start]) || isNaN(array[stop]) ? true : loopalyzer.canInterpolate(array, start, stop)); + if (!interpolate) { + array[index] = treatment.amount; + } + } + }) +} + +/* Returns true if we can interpolate between this start and end */ +loopalyzer.canInterpolate = function(array, start, stop) { + var interpolate = false; + if (array[stop] <= array[start] * interpolationRatio) { + // Falling + if (stop - start < fallingInterpolationGap) interpolate = true; + } else { + // Rising + if (stop - start < risingInterpolationGap && array[start] !== 0) interpolate = true; + } + return interpolate; +} + +/* Returns the carbs treatments array as [date, amount] */ +loopalyzer.getCarbTreatments = function(datastorage, daysToShow) { + var treatments = []; // Holds the treatments [date, amount] + var startDate = moment(daysToShow[0]); + var endDate = moment(daysToShow[daysToShow.length - 1]).add(1, 'days'); + + datastorage.treatments.filter(function(treatment) { return treatment.carbs && treatment.carbs > 0 }).forEach(function(treatment) { + if (moment(treatment.created_at).isBetween(startDate, endDate)) { + treatments.push({ date: treatment.created_at, amount: treatment.carbs }); + } + }) + if (laDebug) console.log('Carb treatments', treatments); + return treatments; +} + +/* Returns the insulin treatments array as [date, amount] */ +loopalyzer.getInsulinTreatments = function(datastorage, daysToShow) { + var treatments = []; // Holds the treatments [date, amount] + var startDate = moment(daysToShow[0]); + var endDate = moment(daysToShow[daysToShow.length - 1]).add(1, 'days'); + + datastorage.treatments.filter(function(treatment) { return treatment.insulin && treatment.insulin > 0 }).forEach(function(treatment) { + if (moment(treatment.created_at).isBetween(startDate, endDate)) { + treatments.push({ date: treatment.created_at, amount: treatment.insulin }); + } + }) + if (laDebug) console.log('Insulin treatments', treatments); + return treatments; +} + +// PREDICTIONS START +// +loopalyzer.getAllTreatmentTimestampsForADay = function(datastorage, day) { + var timestamps = []; + var dayStart = moment(day).startOf('day'); + var carbTreatments = loopalyzer.getCarbTreatments(datastorage, [day]); + var insulinTreatments = loopalyzer.getInsulinTreatments(datastorage, [day]); + carbTreatments.forEach(function(entry) { timestamps.push(entry.date) }); + insulinTreatments.forEach(function(entry) { timestamps.push(entry.date) }); + timestamps.sort(function(a, b) { return (a < b ? -1 : 1) }); + timestamps.splice(0, 0, dayStart.toDate()); // Insert a fake timestamp at midnight so we can show predictions during night + return timestamps; +} + +loopalyzer.getAllPredictionsForADay = function(datastorage, day) { + var predictions = []; + var dayStart = moment(day).startOf('day'); + for (var i = datastorage.devicestatus.length - 1; i >= 0; i--) { + if (datastorage.devicestatus[i].loop && datastorage.devicestatus[i].loop.predicted) { + var predicted = datastorage.devicestatus[i].loop.predicted; + if (moment(predicted.startDate).isSame(dayStart, 'day')) + predictions.push(datastorage.devicestatus[i].loop.predicted); + } else if (datastorage.devicestatus[i].openaps && datastorage.devicestatus[i].openaps.suggested && datastorage.devicestatus[i].openaps.suggested.predBGs) { + var entry = {}; + entry.startDate = datastorage.devicestatus[i].openaps.suggested.timestamp; + // For OpenAPS/AndroidAPS we fall back from COB if present, to UAM, then IOB + if (datastorage.devicestatus[i].openaps.suggested.predBGs.COB) { + entry.values = datastorage.devicestatus[i].openaps.suggested.predBGs.COB; + } else if (datastorage.devicestatus[i].openaps.suggested.predBGs.UAM) { + entry.values = datastorage.devicestatus[i].openaps.suggested.predBGs.UAM; + } else entry.values = datastorage.devicestatus[i].openaps.suggested.predBGs.IOB; + predictions.push(entry); + } + } + // Remove duplicates before we're done + var p = []; + predictions.forEach(function(prediction) { + if (p.length === 0 || prediction.startDate !== p[p.length - 1].startDate) + p.push(prediction); + }) + return p; +} + +/* Find the earliest new predicted instance that has a timestamp equal to or larger than timestamp */ +/* (so if we have bolused or eaten we want to find the prediction that Loop has estimated just after that) */ +/* Returns the index into the predictions array that is the predicted we are looking for */ +loopalyzer.findPredicted = function(predictions, timestamp, offset) { + var ts = moment(timestamp).add(offset, 'minutes'); + var predicted = null; + if (offset && offset < 0) { // If offset is negative, start searching from first prediction going forward + for (let i = 0; i < predictions.length; i++) { + if (predictions[i] && predictions[i].startDate && moment(predictions[i].startDate) <= ts) { + predicted = i; + } + } + } else { // If offset is positive or zero, start searching from last prediction going backward + for (let i = predictions.length - 1; i > 0; i--) { + if (predictions[i] && predictions[i].startDate && moment(predictions[i].startDate) >= ts) { + predicted = i; + } + } + } + return predicted; +} + +loopalyzer.getPredictions = function(datastorage, daysToShow, client) { + + if (!datastorage.devicestatus) + return []; + + var predictedOffset = 0; + var truncatePredictions = true; + + // Fill the bins array with the timestamp, one per 5 minutes + var bins = []; + var date = moment(); + date.set({ 'hours': 0, 'minutes': 0, 'seconds': 0, 'milliseconds': 0 }); + for (var i = 0; i < 288; i++) { + bins.push([date.toDate(), []]); + date.add(5, 'minutes'); + } + + daysToShow.forEach(function(day) { + var p = []; // Array with all prediction SGVs for this day, we'll fill this and then insert into the bins later + for (var i = 0; i < 288; i++) p.push(NaN); + var treatmentTimestamps = loopalyzer.getAllTreatmentTimestampsForADay(datastorage, day); + var predictions = loopalyzer.getAllPredictionsForADay(datastorage, day); + + if (predictions.length > 0 && treatmentTimestamps.length > 0) { + + // Iterate over all treatments, find the predictions for each and add them to the entries array p, aligned on timestamp + for (var treatmentsIndex = 0; treatmentsIndex < treatmentTimestamps.length; treatmentsIndex++) { + var timestamp = treatmentTimestamps[treatmentsIndex]; + var predictedIndex = loopalyzer.findPredicted(predictions, timestamp, predictedOffset); // Find predictions offset before or after timestamp + + if (predictedIndex != null) { + var entry = predictions[predictedIndex]; // Start entry + var d = moment(entry.startDate); + var end = moment(day).endOf('day'); // Default to stop and end of the day + if (truncatePredictions) { + if (predictedOffset >= 0) { + // But if we are looking forward we want to stop at the next treatment + if (treatmentsIndex < treatmentTimestamps.length - 1) { + end = moment(treatmentTimestamps[treatmentsIndex + 1]); + } + } else { + // And if we are looking backward then we want to stop at "this" treatment + end = moment(treatmentTimestamps[treatmentsIndex]); + } + } + for (var entryIndex in entry.values) { + if (!d.isAfter(end)) { + var dayStart = moment(d).startOf('day'); + var minutesAfterMidnight = moment(d).diff(dayStart, 'minutes'); + var index = Math.floor(minutesAfterMidnight / 5); + p[index] = client.utils.scaleMgdl(entry.values[entryIndex]); + d.add(5, 'minutes'); + } + } + } + } + } + for (let i = 0; i < 288; i++) { + bins[i][1].push(p[i]); + } + }) + return bins; +} +// +// PREDICTIONS ENDS + +// VARIOUS UTILITY FUNCTIONS // + +/* Create an empty bins array with date stamps for today */ +loopalyzer.getEmptyBins = function() { + var bins = []; + var todayStart = moment().startOf('day'); + var todayEnd = moment().endOf('day'); + for (var dt = todayStart; dt < todayEnd; dt.add(5, 'minutes')) { + bins.push([dt.toDate(), []]); + } + return bins; +} + +/* Takes an array of 288 values and adds to the bins */ +loopalyzer.addArrayToBins = function(bins, values) { + if (bins && bins.length === 288 && values && values.length === 288) { + values.forEach(function(value, index) { + bins[index][1].push(value); + }); + } else + console.log('addArrayToBins - array must have 288 items', values); +} + +/* Fill all NaNs in an array by interpolating between adjacent values */ +loopalyzer.interpolateArray = function(values, allowNegative) { + var startIndex = 0 + , stopIndex = 0 + , k = 0 + , m = 0; + + while (isNaN(values[startIndex])) { + startIndex++; // Advance start to the first real number + } + stopIndex = startIndex + 1; + while (stopIndex < values.length) { + while (stopIndex < values.length && isNaN(values[stopIndex])) { + stopIndex++; // Advance stop to the first real number after start + } + if (stopIndex < values.length) { + // Now we have real numbers at start and stop and NaNs in between + // Compute the y=k*x+m = (y2-y1)/(x2-x1)*x+y1 + // Only interpolate if decreasing or steady, newer on increasing + if (values[stopIndex] <= values[startIndex]) { + k = (values[stopIndex] - values[startIndex]) / (stopIndex - startIndex); + m = values[startIndex]; + } + for (var x = 0; x < (stopIndex - startIndex); x++) { + values[x + startIndex] = k * x + m; + if (!allowNegative && values[x + startIndex] < 0) { + values[x + startIndex] = 0; + } + } + startIndex = stopIndex; + stopIndex++; + } + } +} + +/* Compute min value in bins */ +loopalyzer.min = function(xBins) { + var min = xBins[0][1]; + for (var i = 0; i < xBins.length; i++) { + if (isNaN(min) || min === null) min = xBins[i][1]; + if (!isNaN(xBins[i][1]) && xBins[i][1] < min) min = xBins[i][1]; + } + return min; +} + +/* Compute max value in bins */ +loopalyzer.max = function(xBins) { + var max = xBins[0][1]; + for (var i = 0; i < xBins.length; i++) { + if (isNaN(max) || max === null) max = xBins[i][1]; + if (!isNaN(xBins[i][1]) && xBins[i][1] > max) max = xBins[i][1]; + } + return max; +} + +/* Compute avg value in bins */ +loopalyzer.avg = function(xBins) { + var out = []; + xBins.forEach(function(entry) { + var sum = 0; + var count = 0; + entry[1].forEach(function(value) { + if (value && !isNaN(value)) { + sum += value; + count++; + } + }) + var avg = sum / count; + out.push([entry[0], avg]); + }) + return out; +} + +// Timeshifts a bins array with subarrays for multiple days +loopalyzer.timeShiftBins = function(bins, timeShift) { + if (bins && bins.length > 0) { + timeShift.forEach(function(minutes, dayIndex) { + if (minutes !== 0) { + var tempBin = []; + bins.forEach(function() { + tempBin.push(NaN); // Fill tempBin with NaNs + }) + var minutesBy5 = Math.floor(minutes / 5); + if (minutesBy5 > 0) { + let count = 288 - minutesBy5; + // If minutes>0 it means we should shift forward in time + // Example: Shift by 15 mins = 3 buckets + // bin : 0 1 2 3 4 5 6 7 8 9 10 + // tempBin: NaN NaN NaN 0 1 2 3 4 5 6 7 + for (let i = 0; i < count; i++) { + tempBin[i + minutesBy5] = bins[i][1][dayIndex]; + } + } + if (minutesBy5 < 0) { + let count = 288 + minutesBy5; + // If minutes<0 it means we should shift backward in time + // Example: Shift by 15 mins = 3 buckets + // bin : 0 1 2 3 4 5 6 7 8 9 10 + // tempBin: 3 4 5 6 7 8 9 10 NaN NaN NaN + for (var i = 0; i < count; i++) { + tempBin[i] = bins[i - minutesBy5][1][dayIndex]; + } + } + // Put the shifted data back into original bins variable (pass by pointer) + for (let i = 0; i < 288; i++) { + bins[i][1][dayIndex] = tempBin[i]; + } + } + }); + } +} + +// Modifies the timestamp in the bin by timeShift minutes, for each day +loopalyzer.timeShiftSingleBin = function(bin, daysToShow, timeShift) { + if (bin && bin.length > 0) { + daysToShow.forEach(function(day, dayIndex) { + var minutesToAdd = timeShift[dayIndex]; + var date = moment(day); + bin.forEach(function(entry, entryIndex) { + var entryDate = moment(entry.date); + if (entryDate.isSame(date, 'day')) { + entryDate.add(minutesToAdd, 'minutes'); + bin[entryIndex].date = entryDate.toDate(); + } + }) + }) + } +} + +/* Returns true if the profile values in a is identical to values in b, false otherwise */ +loopalyzer.isSameProfileValues = function(a, b) { + // Because the order of the keys are random when stringifying we do our own custom stringify ourselves + var aString = ''; + var bString = ''; + if (a.basal) { + aString += 'basal:'; + a.basal.forEach(function(entry) { + aString += 's' + entry.timeAsSeconds + 't' + entry.time + 'v' + entry.value; + }) + } + if (a.carbratio) { + aString += 'carbratio:'; + a.carbratio.forEach(function(entry) { + aString += 's' + entry.timeAsSeconds + 't' + entry.time + 'v' + entry.value; + }) + } + if (a.sens) { + aString += 'sens:'; + a.sens.forEach(function(entry) { + aString += 's' + entry.timeAsSeconds + 't' + entry.time + 'v' + entry.value; + }) + } + if (b.basal) { + bString += 'basal:'; + b.basal.forEach(function(entry) { + bString += 's' + entry.timeAsSeconds + 't' + entry.time + 'v' + entry.value; + }) + } + if (b.carbratio) { + bString += 'carbratio:'; + b.carbratio.forEach(function(entry) { + bString += 's' + entry.timeAsSeconds + 't' + entry.time + 'v' + entry.value; + }) + } + if (b.sens) { + bString += 'sens:'; + b.sens.forEach(function(entry) { + bString += 's' + entry.timeAsSeconds + 't' + entry.time + 'v' + entry.value; + }) + } + return (aString == bString); +} + +loopalyzer.renderProfilesTable = function(datastoreProfiles, daysToShow, client) { + + // Loop thru the daysToShow and get the timestamp of the first day displayed + var beginningOfFirstDay = null; + var endOfLastDay = null; + daysToShow.forEach(function(day) { + var dayStart = moment(day).startOf('day'); + var dayEnd = moment(day).endOf('day'); + if (!beginningOfFirstDay || dayStart < beginningOfFirstDay) + beginningOfFirstDay = dayStart; + if (!endOfLastDay || dayEnd > endOfLastDay) + endOfLastDay = dayEnd; + }); + + // Now some profile juggling... We want to display only the profiles relevant to the days we are showing. + // This includes the last profile created before the first display date, and the profiles created on the display dates. + // However we don't want to show duplicate profiles and we also don't want to show more than just a few if there are many. + + // First, extract the profiles that have a startDate less than the endOfLastDay as only these are relevant, and sort + // these on ascending startDate (create a clone array so we don't modify the Store array). And only save the profiles + // that have basal, carbratio, or sens. + var profilesArray1 = []; + datastoreProfiles.forEach(function(entry) { + var newEntry = {}; + newEntry.startDate = entry.startDate; + var store = entry.store; + if (store) { + for (var key in store) { + if (laDebug) console.log('profile ' + key); + // eslint-disable-next-line no-prototype-builtins + if (store.hasOwnProperty(key)) { + var defaultProfile = store[key]; + newEntry.profileName = key; + if (defaultProfile.basal) newEntry.basal = defaultProfile.basal; + if (defaultProfile.carbratio) newEntry.carbratio = defaultProfile.carbratio; + if (defaultProfile.sens) newEntry.sens = defaultProfile.sens; + if ((newEntry.basal || newEntry.carbratio || newEntry.sens) && moment(entry.startDate).isBefore(endOfLastDay)) + profilesArray1.push(newEntry); + } + } + } + }) + profilesArray1.sort(function(a, b) { return (a.startDate > b.startDate ? 1 : -1) }); // Ascending + if (laDebug) { + profilesArray1.forEach(function(entry) { + console.log('profilesArray1 - ' + entry.startDate); + }) + } + if (laDebug) console.log('profilesArray1 has ' + profilesArray1.length + ' profiles'); + + // Second, the deduplication - remove all duplicates which have a later startDate but identical data + var profilesArray2 = []; + var profileToCompareWith = profilesArray1[0]; + profilesArray2.push(profileToCompareWith); // Push the first profile, which should always be included. + profilesArray1.forEach(function(entry) { + if (laDebug) { + console.log('Comparing ' + JSON.stringify(profileToCompareWith.startDate) + ' to ' + JSON.stringify(entry.startDate)); + console.log(profileToCompareWith, entry); + } + if (!loopalyzer.isSameProfileValues(profileToCompareWith, entry)) { + profilesArray2.push(entry); + profileToCompareWith = entry; + if (laDebug) + console.log('ADDING IT'); + } else { + // Do NOT push the entry to profilesArray2, and keep comparing with the same (olders unique) profile + if (laDebug) + console.log('SKIPPING IT'); + } + }) + if (laDebug) console.log('profilesArray2 has ' + profilesArray2.length + ' profiles'); + + // Sort the newest Profile first + profilesArray2.sort(function(a, b) { return (a.startDate > b.startDate ? 1 : -1) }); // Ascending + + // Third, find the latest profile with a startDate before beginningOfFirstDay + var latestProfile = profilesArray2[0]; // This is the oldest one + profilesArray2.forEach(function(entry) { + if (laDebug) + console.log(entry.startDate + ' isBefore ' + beginningOfFirstDay + ' = ' + moment(entry.startDate).isBefore(beginningOfFirstDay)); + if (moment(entry.startDate).isBefore(beginningOfFirstDay)) + latestProfile = entry; + }); + if (laDebug) console.log('latest profile is ' + latestProfile.startDate); + + // Now create a final array with the latest profile found above and add all + // the other profiles with a startDate between beginningOfFirstDay and endOfLastDay + var profiles = []; + profiles.push(latestProfile); // Add the latest one + profilesArray2.forEach(function(entry) { + if (laDebug) + console.log(entry.startDate + ' isAfter ' + beginningOfFirstDay + ' = ' + moment(entry.startDate).isAfter(beginningOfFirstDay)); + if (moment(entry.startDate).isAfter(beginningOfFirstDay)) + profiles.push(entry); // Add the profile if it's between beginning and end of show dates + }); + + // Now we have an array of all the profiles that are relevant for the days we are displaying. + if (laDebug) { + profiles.forEach(function(entry) { + console.log('profiles - ' + entry.startDate); + }) + } + if (laDebug) console.log('profiles has ' + profiles.length + ' profiles'); + + var translate = client.translate; + var tableHtml = ''; + + profiles.forEach(function(theProfile, index) { + + if (index < 3) { + tableHtml += ''; + + } else + if (index == 3) { + // Add ellipsis if too many profiles to display, but only one ellipsis even if there are more profiles + tableHtml += ''; + } + }); + + // Close the entire table + tableHtml += '
'; + tableHtml += ''; + tableHtml += ''; + tableHtml += ''; + + // Add Basal as a table in the first td + tableHtml += ''; + + // Add Carb Ratio as a table in the second td + tableHtml += ''; + + // Add Sensitivity as a table in the third td + tableHtml += ''; + + // Close theProfile table + tableHtml += '
' + theProfile.profileName + ' (' + new Date(theProfile.startDate).toLocaleString() + ')
' + translate('Basal') + '' + translate('Carb ratio') + '' + translate('Sensitivity') + '
'; + if (theProfile.basal) { + theProfile.basal.forEach(function(entry) { + tableHtml += '' + }); + } + tableHtml += '
' + entry.time + '' + parseFloat(entry.value).toFixed(3) + '
'; + if (theProfile.carbratio) { + theProfile.carbratio.forEach(function(entry) { + tableHtml += '' + }); + } + tableHtml += '
' + entry.time + '' + parseFloat(entry.value).toFixed(1) + '
'; + if (theProfile.sens) { + theProfile.sens.forEach(function(entry) { + tableHtml += '' + }); + } + tableHtml += '
' + entry.time + '' + parseFloat(entry.value).toFixed(1) + '
.....
'; + + // And add our HTML to the view + $("#loopalyzer-profiles").html(tableHtml); + +}; + +// Main method +loopalyzer.report = function(datastorage, sorteddaystoshow, options) { + if (laDebug) console.log('Loopalyzer ' + laVersion); + + // Copy the sorteddaystoshow into new array (clone) and re-sort ascending (so we don't mess with original array) + var daysToShow = []; + sorteddaystoshow.forEach(function(day) { daysToShow.push(day) }); + daysToShow.sort(function(a, b) { return (a < b ? -1 : 1) }); // We always want them chronological order + + var firstDay = moment(daysToShow[0]); + var lastDay = moment(daysToShow[daysToShow.length - 1]); + var days = lastDay.diff(firstDay, 'day') + 1; + if (laDebug) console.log('Loopalyzer ' + firstDay.format() + ' - ' + lastDay.format() + ' is ' + days + ' days'); + if (days <= 14) { + $("#loopalyzer-notenoughdata").hide(); + $("#loopalyzer-dateinfo").show(); + $("#loopalyzer-buttons").show(); + $("#loopalyzer-charts").show(); + $("#loopalyzer-profiles-table").show(); + $("#loopalyzer-help").hide(); + loopalyzer.generateReport(datastorage, daysToShow, options); + } else { + $("#loopalyzer-notenoughdata").show(); + $("#loopalyzer-dateinfo").hide(); + $("#loopalyzer-buttons").hide(); + $("#loopalyzer-charts").hide(); + $("#loopalyzer-profiles-table").hide(); + $("#loopalyzer-help").hide(); + } +} + +loopalyzer.generateReport = function(datastorage, daysToShow, options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var translate = client.translate; + var profile = client.sbx.data.profile; + // var report_plugins = Nightscout.report_plugins; + // var scaledTreatmentBG = report_plugins.utils.scaledTreatmentBG; + + var today = new Date(); + var todayJSON = { 'year': today.getFullYear(), 'month': today.getMonth(), 'date': today.getDate() }; + + var dateInfo = moment(daysToShow[0]).format('ddd MMM D'); // .split(',')[0]; + if (daysToShow.length > 1) dateInfo += ' - ' + moment(daysToShow[daysToShow.length - 1]).format('ddd MMM D'); // .split(',')[0]; + $("#loopalyzer-dateinfo").html(dateInfo); + + loopalyzer.prepareHtml(); + $("#loopalyzer-buttons").show(); + if (daysToShow.length == 1) { + // Disable and gray out timeShift if only a single day + $("#rp_loopalyzertimeshift").prop('checked', false); + $("#rp_loopalyzertimeshift").attr("disabled", true); + $("#rp_loopalyzermincarbs").attr("disabled", true); + $("#rp_loopalyzert1").attr("disabled", true); + $("#rp_loopalyzert2").attr("disabled", true); + $("#rp_loopalyzertimeshiftinput").css('color', 'gray'); + } else { + // Enable and turn the timeShift black if multiple days + $("#rp_loopalyzertimeshift").removeAttr("disabled"); + $("#rp_loopalyzermincarbs").removeAttr("disabled"); + $("#rp_loopalyzert1").removeAttr("disabled"); + $("#rp_loopalyzert2").removeAttr("disabled"); + $("#rp_loopalyzertimeshiftinput").css('color', 'black'); + } + // Check if there is data in the profiles and render the profiles table if there is + if ($("#rp_loopalyzerprofiles").is(":checked") && (datastorage.profiles && datastorage.profiles.length > 0)) { + $("#loopalyzer-profiles-table").show(); + loopalyzer.renderProfilesTable(datastorage.profiles, daysToShow, client); + } else + $("#loopalyzer-profiles-table").hide(); + + // Pull all necessary treatment information + profile.updateTreatments(datastorage.profileSwitchTreatments, datastorage.tempbasalTreatments, datastorage.combobolusTreatments); + + var carbTreatments = loopalyzer.getCarbTreatments(datastorage, daysToShow); + var insulinTreatments = loopalyzer.getInsulinTreatments(datastorage, daysToShow); + var sgvBin = loopalyzer.getSGVs(datastorage, daysToShow); + var basalsBin = loopalyzer.getBasals(datastorage, daysToShow, profile); + var tempBasalsBin = loopalyzer.getTempBasalDeltas(datastorage, daysToShow, profile); + var iobBin = loopalyzer.getIOBs(datastorage, daysToShow, profile, client, insulinTreatments); + var cobBin = loopalyzer.getCOBs(datastorage, daysToShow, profile, client, carbTreatments); + var predictionsBin = []; + + if ($("#rp_loopalyzerpredictions").is(":checked")) { + predictionsBin = loopalyzer.getPredictions(datastorage, daysToShow, client); + } + + // Prepare an array with the minutes to timeShift each day (0 as default since timeShift is off by default) + var timeShifts = []; + var firstCarbs = []; + var timeShiftStartTime = null; // If timeShifting this is the average time the meals were eaten + var timeShiftStopTime = null; // and this is the start + DIA according to profile + var doTimeShift = false; + daysToShow.forEach(function() { timeShifts.push(0); + firstCarbs.push(NaN) }); + + // Check to see if we are doing timeShift or not + if ($("#rp_loopalyzertimeshift").is(":checked") && daysToShow.length > 1) { + var mealMinCarbs = $("#rp_loopalyzermincarbs").val(); + var t1 = $("#rp_loopalyzert1").val(); + var t2 = $("#rp_loopalyzert2").val(); + + if (t2 > t1) { + var h1 = t1.split(':')[0]; + var m1 = t1.split(':')[1]; + var h2 = t2.split(':')[0]; + var m2 = t2.split(':')[1]; + + var timeShiftBegin = moment(); + timeShiftBegin.set({ 'hours': h1, 'minutes': m1, 'seconds': 0 }); + + var timeShiftEnd = moment(); + timeShiftEnd.set({ 'hours': h2, 'minutes': m2, 'seconds': 0 }); + + //Loop through the carb treatments and find the first meal each day + daysToShow.forEach(function(day, dayIndex) { + var timeShiftBegin = moment(day); + var timeShiftEnd = moment(day); + timeShiftBegin.set({ 'hours': h1, 'minutes': m1, 'seconds': 0 }); + timeShiftEnd.set({ 'hours': h2, 'minutes': m2, 'seconds': 0 }); + + var found = false; + carbTreatments.forEach(function(entry) { + if (!found && entry.amount >= mealMinCarbs) { + var date = moment(entry.date); + if ((date.isSame(timeShiftBegin, 'minute') || date.isAfter(timeShiftBegin, 'minute')) && + (date.isSame(timeShiftEnd, 'minute') || date.isBefore(timeShiftEnd, 'minute'))) { + var startOfDay = moment(entry.date); + startOfDay.set({ 'hours': 0, 'minutes': 0, 'seconds': 0 }); + var minutesAfterMidnight = date.diff(startOfDay, 'minutes'); + firstCarbs[dayIndex] = minutesAfterMidnight; + found = true; + doTimeShift = true; + } + } + }) + }) + + // Calculate the average starting time, in minutes after midnight + var sum = 0 + , count = 0; + + firstCarbs.forEach(function(minutesAfterMidnight) { + if (minutesAfterMidnight) { // Avoid NaN + sum += minutesAfterMidnight; + count++; + } + }); + + var averageMinutesAfterMidnight = Math.round(sum / count); + + var dia = profile.getDIA(); + if (!dia || dia <= 0) + dia = 6; // Default to 6h if DIA not set in profile + timeShiftStartTime = moment(todayJSON); + timeShiftStartTime.minutes(averageMinutesAfterMidnight); + timeShiftStopTime = moment(todayJSON); + if (averageMinutesAfterMidnight + dia * 60 < 24 * 60) + timeShiftStopTime.minutes(averageMinutesAfterMidnight + dia * 60); // If not beyond midnight, stop at end of DIA + else + timeShiftStopTime.minutes(24 * 60 - 1); // If beyond midnight, stop at midnight + + // Compute the timeShift (+ / -) that we should add to each entry (sgv, iob, carbs, etc) for each day + firstCarbs.forEach(function(minutesAfterMidnight, index) { + if (minutesAfterMidnight) { // Avoid NaN + var delta = Math.round(averageMinutesAfterMidnight - minutesAfterMidnight); + timeShifts[index] = delta; + } + }); + + if (doTimeShift) { + loopalyzer.timeShiftBins(sgvBin, timeShifts); + loopalyzer.timeShiftBins(basalsBin, timeShifts); + loopalyzer.timeShiftBins(tempBasalsBin, timeShifts); + loopalyzer.timeShiftBins(iobBin, timeShifts); + loopalyzer.timeShiftBins(cobBin, timeShifts); + loopalyzer.timeShiftBins(predictionsBin, timeShifts); + loopalyzer.timeShiftSingleBin(carbTreatments, daysToShow, timeShifts); + loopalyzer.timeShiftSingleBin(insulinTreatments, daysToShow, timeShifts); + } + } else { + console.log('Loopalyzer - Timeshift end must be later than beginning.'); + } + } + + // After timeShift code block, get the average values + var sgvAvg = loopalyzer.avg(sgvBin); + var basalsAvg = loopalyzer.avg(basalsBin); + var tempBasalsAvg = loopalyzer.avg(tempBasalsBin); + var iobAvg = loopalyzer.avg(iobBin); + var cobAvg = loopalyzer.avg(cobBin); + var predictionsAvg = loopalyzer.avg(predictionsBin); + + var high = options.targetHigh; + var low = options.targetLow; + + // Set up the charts basics + function tickFormatter (val, axis) { + if (val <= axis.min) { return ''; } + if (val >= axis.max) { return ''; } + return val + ''; + } + + var tickColor = '#DDDDDD'; + var basalColor = '#33A0FF'; + var glucoseColor = '#33AA33'; + var predictionsColor = '#8E1578'; + var glucoseRangeColor = '#D6FFD6'; + var insulinColor = '#FF7000'; + var carbColor = '#23D820'; + var timeShiftBackgroundColor = "#F3F3F3"; + var barWidth = (24 * 60 * 60 * 1000 / 288); + var borderWidth = 1; + var labelWidth = 25; + var xaxisCfg = { + mode: 'time' + , timezone: 'browser' + , timeformat: '%H:%M' + , tickColor: tickColor + , tickSize: [1, "hour"] + , font: { size: 0 } + }; + + var hiddenAxis = { + position: "right" + , show: true + , labelWidth: 10 + , tickColor: "#FFFFFF" + , font: { size: 0 } + } + + // For drawing the carbs and insulin treatments + var markings = []; + var markingColor = "#000000"; + + // Chart 1: Basal + markings = []; + if (doTimeShift) + markings.push({ xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate() }, color: timeShiftBackgroundColor }); + var chartBasalData = [{ + data: basalsAvg + , label: translate('Basal profile') + , id: 'basals' + , color: basalColor + , points: { show: false } + , bars: { show: true, fill: true, barWidth: barWidth } + , yaxis: 1 + }]; + var chartBasalOptions = { + xaxis: xaxisCfg + , yaxes: [{ + tickColor: tickColor + , labelWidth: labelWidth + , tickFormatter: function(val, axis) { return tickFormatter(val, axis); } + } + , hiddenAxis] + , grid: { + borderWidth: borderWidth + , markings: markings + } + }; + $.plot('#loopalyzer-basal', chartBasalData, chartBasalOptions); + + // Chart 2: Blood glucose + markings = []; + if (doTimeShift) + markings.push({ xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate() }, color: timeShiftBackgroundColor }); + markings.push({ yaxis: { from: low, to: high }, color: glucoseRangeColor }); + + var chartBGData = [{ + label: translate('Blood glucose') + , data: sgvAvg + , id: 'glucose' + , color: glucoseColor + , points: { show: false } + , lines: { show: true } + }]; + if (predictionsAvg && predictionsAvg.length > 0) { + chartBGData.push({ + label: translate('Predictions') + , data: predictionsAvg + , id: 'predictions' + , color: predictionsColor + , points: { show: true, fill: true, radius: 0.75, fillColor: predictionsColor } + , lines: { show: false } + }); + } + var chartBGOptions = { + xaxis: xaxisCfg + , yaxes: [{ + min: 0 + , max: options.units === 'mmol' ? 20 : 400 + , tickColor: tickColor + , labelWidth: labelWidth + , tickFormatter: function(val, axis) { return tickFormatter(val, axis); } + } + , hiddenAxis] + , grid: { + borderWidth: borderWidth + , markings: markings + } + }; + $.plot('#loopalyzer-bg', chartBGData, chartBGOptions); + + // Chart 3: Delta temp basals + markings = []; + if (doTimeShift) + markings.push({ xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate() }, color: timeShiftBackgroundColor }); + markings.push({ yaxis: { from: 0, to: 0 }, color: insulinColor, lineWidth: 2 }); + + var chartTempBasalData = [{ + data: tempBasalsAvg + , label: translate('Temp basal delta') + , id: 'tempBasals' + , color: insulinColor + , points: { show: false } + , bars: { show: true, barWidth: barWidth } + }]; + var chartTempBasalOptions = { + xaxis: xaxisCfg + , yaxes: [{ + tickColor: tickColor + , labelWidth: labelWidth + , tickFormatter: function(val, axis) { return tickFormatter(val, axis); } + } + , hiddenAxis] + , grid: { + borderWidth: borderWidth + , markings: markings + } + }; + $.plot('#loopalyzer-tempbasal', chartTempBasalData, chartTempBasalOptions); + + // Chart 4: IOB + markings = []; + if (doTimeShift) + markings.push({ xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate() }, color: timeShiftBackgroundColor }); + insulinTreatments.forEach(function(treatment) { + var startDate = moment(treatment.date); + var endDate = moment(treatment.date); + startDate.set(todayJSON); + endDate.set(todayJSON); + endDate.add(5, 'minutes'); + markings.push({ xaxis: { from: startDate.toDate(), to: endDate.toDate() }, yaxis: { from: 0, to: treatment.amount }, color: markingColor }); + }) + + var chartIOBData = [{ + data: iobAvg + , label: translate('IOB') + , id: 'iobs' + , color: insulinColor + , points: { show: false } + , bars: { show: true, fill: true, barWidth: barWidth } + }]; + var chartIOBOptions = { + xaxis: xaxisCfg + , yaxes: [{ + tickColor: tickColor + , labelWidth: labelWidth + , tickFormatter: function(val, axis) { return tickFormatter(val, axis); } + } + , hiddenAxis] + , grid: { + borderWidth: borderWidth + , markings: markings + } + }; + $.plot('#loopalyzer-iob', chartIOBData, chartIOBOptions); + + // Chart 5: COB + markings = []; + if (doTimeShift) + markings.push({ xaxis: { from: timeShiftStartTime.toDate(), to: timeShiftStopTime.toDate() }, color: timeShiftBackgroundColor }); + carbTreatments.forEach(function(treatment) { + var startDate = moment(treatment.date); + var endDate = moment(treatment.date); + startDate.set(todayJSON); + endDate.set(todayJSON); + endDate.add(5, 'minutes'); + markings.push({ xaxis: { from: startDate.toDate(), to: endDate.toDate() }, yaxis: { from: 0, to: treatment.amount }, color: markingColor }); + }) + delete xaxisCfg.font; // Remove the font config so HH:MM is shown on the last chart + + var chartCOBData = [{ + data: cobAvg + , label: translate('COB') + , id: 'cobs' + , color: carbColor + , points: { show: false } + , bars: { show: true, fil: true, barWidth: barWidth } + }]; + var chartCOBOptions = { + xaxis: xaxisCfg + , yaxes: [{ + tickColor: tickColor + , labelWidth: labelWidth + , tickFormatter: function(val, axis) { return tickFormatter(val, axis); } + } + , hiddenAxis] + , grid: { + borderWidth: borderWidth + , markings: markings + } + }; + $.plot('#loopalyzer-cob', chartCOBData, chartCOBOptions); + +}; diff --git a/lib/report_plugins/profiles.js b/lib/report_plugins/profiles.js index c31b8b2fa9a..580367891de 100644 --- a/lib/report_plugins/profiles.js +++ b/lib/report_plugins/profiles.js @@ -6,33 +6,32 @@ var profiles = { , pluginType: 'report' }; -function init() { +function init () { return profiles; } module.exports = init; -profiles.html = function html(client) { +profiles.html = function html (client) { var translate = client.translate; var ret = - '

' + translate('Profiles') + '

' - + '
' + translate('Database records') + ' ' - + '
' - + '
' - + '
' - + '
' - ; + '

' + translate('Profiles') + '

' + + '
' + translate('Database records') + ' ' + + '
' + + '
' + + '
' + + '
'; return ret; }; profiles.css = - '#profiles-chart {' - + ' width: 100%;' - + ' height: 100%;' - + '}' - ; + '#profiles-chart {' + + ' width: 100%;' + + ' height: 100%;' + + '}'; -profiles.report = function report_profiles(datastorage, sorteddaystoshow, options) { +// eslint-disable-next-line no-unused-vars +profiles.report = function report_profiles (datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; @@ -41,10 +40,10 @@ profiles.report = function report_profiles(datastorage, sorteddaystoshow, option var databaseRecords = $('#profiles-databaserecords'); databaseRecords.empty(); - for (var r = 0; r < profileRecords.length; r++ ) { + for (var r = 0; r < profileRecords.length; r++) { databaseRecords.append(''); } - databaseRecords.unbind().bind('change',recordChange); + databaseRecords.unbind().bind('change', recordChange); recordChange(); @@ -58,11 +57,10 @@ profiles.report = function report_profiles(datastorage, sorteddaystoshow, option var tr = $(''); $('#profiles-default').val(currentrecord.defaultProfile); - for (var key in currentrecord.store) { - if (currentrecord.store.hasOwnProperty(key)) { - tr.append(displayRecord(currentrecord.store[key], key)) - } - } + + Object.keys(currentrecord.store).forEach(key => { + tr.append(displayRecord(currentrecord.store[key], key)); + }); table.append(tr); @@ -73,7 +71,7 @@ profiles.report = function report_profiles(datastorage, sorteddaystoshow, option } } - function displayRecord(record, name) { + function displayRecord (record, name) { var td = $(''); var table = $(''); @@ -91,7 +89,7 @@ profiles.report = function report_profiles(datastorage, sorteddaystoshow, option return td; } - function displayRanges(array, array2) { + 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 : '') + '
'; diff --git a/lib/report_plugins/success.js b/lib/report_plugins/success.js index d4ee4b9173e..a08ef8d045e 100644 --- a/lib/report_plugins/success.js +++ b/lib/report_plugins/success.js @@ -8,66 +8,60 @@ var success = { , pluginType: 'report' }; -function init() { +function init () { return success; } module.exports = init; -success.html = function html(client) { +success.html = function html (client) { var translate = client.translate; var ret = - '

' + translate('Weekly Success') + '

' - + '
' - ; + '

' + translate('Weekly Success') + '

' + + '
'; return ret; }; -success.css = - '#success-placeholder td {'+ - ' border: 1px #ccc solid;'+ - ' margin: 0;'+ - ' padding: 1px;'+ - ' text-align:center;'+ - '}'+ - '#success-placeholder .bad {'+ - ' background-color: #fcc;'+ - '}'+ - - '#success-placeholder .good {'+ - ' background-color: #cfc;'+ - '}'+ - - '#success-placeholder th:first-child {'+ - ' width: 30%;'+ - '}'+ - '#success-placeholder th {'+ - ' width: 10%;'+ - '}'+ - '#success-placeholder table {'+ - ' width: 100%;'+ - '}' - ; - - - -success.report = function report_success(datastorage, sorteddaystoshow, options) { +success.css = + `#success-placeholder td { + border: 1px #ccc solid; + margin: 0; + padding: 1px; + text-align:center; + } + #success-placeholder .bad { + background-color: #fcc; + } + #success-placeholder .good { + background-color: #cfc; + } + #success-placeholder th:first-child { + width: 30%; + } + #success-placeholder th { + width: 10%; + } + #success-placeholder table { + width: 100%; + }`; + +success.report = function report_success (datastorage, sorteddaystoshow, options) { var Nightscout = window.Nightscout; var client = Nightscout.client; var translate = client.translate; var ss = require('simple-statistics'); - var low = options.targetLow, - high = options.targetHigh; + var low = options.targetLow + , high = options.targetHigh; var data = datastorage.allstatsrecords; - + var now = Date.now(); var period = 7 * times.hours(24).msecs; var firstDataPoint = data.reduce(function(min, record) { - return Math.min(min, record.displayTime); - }, Number.MAX_VALUE); + return Math.min(min, record.displayTime); + }, Number.MAX_VALUE); if (firstDataPoint < 1390000000000) { firstDataPoint = 1390000000000; } @@ -79,39 +73,39 @@ success.report = function report_success(datastorage, sorteddaystoshow, options) if (quarters === 0) { // insufficent data - grid.append('

'+translate('There is not sufficient data to run this report. Select more days.')+'

'); + grid.append('

' + translate('There is not sufficient data to run this report. Select more days.') + '

'); return; } var dim = function(n) { var a = []; for (var i = 0; i < n; i++) { - a[i]=0; + a[i] = 0; } return a; }; var sum = function(a) { - return a.reduce(function(sum,v) { - return sum+v; + return a.reduce(function(sum, v) { + return sum + v; }, 0); }; var averages = { - percentLow: 0, - percentInRange: 0, - percentHigh: 0, - standardDeviation: 0, - lowerQuartile: 0, - upperQuartile: 0, - average: 0 + percentLow: 0 + , percentInRange: 0 + , percentHigh: 0 + , standardDeviation: 0 + , lowerQuartile: 0 + , upperQuartile: 0 + , average: 0 }; quarters = dim(quarters).map(function(blank, n) { - var starting = new Date(now - (n+1) * period), - ending = new Date(now - n * period); + var starting = new Date(now - (n + 1) * period) + , ending = new Date(now - n * period); return { - starting: starting, - ending: ending, - records: data.filter(function(record) { - return record.displayTime > starting && record.displayTime <= ending; + starting: starting + , ending: ending + , records: data.filter(function(record) { + return record.displayTime > starting && record.displayTime <= ending; }) }; }).filter(function(quarter) { @@ -121,8 +115,8 @@ success.report = function report_success(datastorage, sorteddaystoshow, options) return record.sgv; }); quarter.standardDeviation = ss.standard_deviation(bgValues); - quarter.average = bgValues.length > 0? (sum(bgValues) / bgValues.length): 'N/A'; - quarter.lowerQuartile = ss.quantile(bgValues, 0.25); + quarter.average = bgValues.length > 0 ? (sum(bgValues) / bgValues.length) : 'N/A'; + quarter.lowerQuartile = ss.quantile(bgValues, 0.25); quarter.upperQuartile = ss.quantile(bgValues, 0.75); quarter.numberLow = bgValues.filter(function(bg) { return bg < low; @@ -148,9 +142,9 @@ success.report = function report_success(datastorage, sorteddaystoshow, options) var lowComparison = function(quarter, averages, field, invert) { if (quarter[field] < averages[field] * 0.8) { - return (invert? 'bad': 'good'); + return (invert ? 'bad' : 'good'); } else if (quarter[field] > averages[field] * 1.2) { - return (invert? 'good': 'bad'); + return (invert ? 'good' : 'bad'); } else { return ''; } @@ -172,44 +166,44 @@ success.report = function report_success(datastorage, sorteddaystoshow, options) } }; - table.append(''); + table.append(''); table.append('' + quarters.filter(function(quarter) { return quarter.records.length > 0; }).map(function(quarter) { var INVERT = true; return '' + [ - quarter.starting.toLocaleDateString() + ' - ' + quarter.ending.toLocaleDateString(), - { - klass: lowComparison(quarter, averages, 'percentLow'), - text: Math.round(quarter.percentLow) + '%' - }, - { - klass: lowComparison(quarter, averages, 'percentInRange', INVERT), - text: Math.round(quarter.percentInRange) + '%' - }, - { - klass: lowComparison(quarter, averages, 'percentHigh'), - text: Math.round(quarter.percentHigh) + '%' - }, - { - klass: lowComparison(quarter, averages, 'standardDeviation'), - text: (quarter.standardDeviation > 10? Math.round(quarter.standardDeviation): quarter.standardDeviation.toFixed(1)) - }, - { - klass: lowQuartileEvaluation(quarter, averages), - text: quarter.lowerQuartile - }, - { - klass: lowComparison(quarter, averages, 'average'), - text: quarter.average.toFixed(1) - }, - { - klass: upperQuartileEvaluation(quarter, averages), - text: quarter.upperQuartile + quarter.starting.toLocaleDateString() + ' - ' + quarter.ending.toLocaleDateString() + , { + klass: lowComparison(quarter, averages, 'percentLow') + , text: Math.round(quarter.percentLow) + '%' + } + , { + klass: lowComparison(quarter, averages, 'percentInRange', INVERT) + , text: Math.round(quarter.percentInRange) + '%' + } + , { + klass: lowComparison(quarter, averages, 'percentHigh') + , text: Math.round(quarter.percentHigh) + '%' + } + , { + klass: lowComparison(quarter, averages, 'standardDeviation') + , text: (quarter.standardDeviation > 10 ? Math.round(quarter.standardDeviation) : quarter.standardDeviation.toFixed(1)) + } + , { + klass: lowQuartileEvaluation(quarter, averages) + , text: quarter.lowerQuartile + } + , { + klass: lowComparison(quarter, averages, 'average') + , text: quarter.average.toFixed(1) + } + , { + klass: upperQuartileEvaluation(quarter, averages) + , text: quarter.upperQuartile } ].map(function(v) { if (typeof v === 'object') { - return ''; + return ''; } else { return ''; } diff --git a/lib/report_plugins/treatments.js b/lib/report_plugins/treatments.js index e6221c8b656..c0215b59b4a 100644 --- a/lib/report_plugins/treatments.js +++ b/lib/report_plugins/treatments.js @@ -44,6 +44,16 @@ treatments.html = function html(client) { + ' ' + ' ' + '
' + + ' ' + + '
' + + ' ' + + '
' + ' ').should.be.greaterThan(-1); //dailystats //TODO FIXME result.indexOf('td class="tdborder" style="background-color:#8f8">Normal: ').should.be.greaterThan(-1); // distribution result.indexOf('').should.be.greaterThan(-1); // hourlystats @@ -276,4 +278,58 @@ describe('reports', function ( ) { }); }); + it ('should produce week to week report', function (done) { + var client = window.Nightscout.client; + + var hashauth = require('../lib/hashauth'); + hashauth.init(client,$); + hashauth.verifyAuthentication = function mockVerifyAuthentication(next) { + hashauth.authenticated = true; + next(true); + }; + + window.confirm = function mockConfirm () { + return true; + }; + + window.alert = function mockAlert () { + return true; + }; + + window.setTimeout = function mockSetTimeout (call) { + call(); + }; + + client.init(function afterInit ( ) { + client.dataUpdate(nowData); + + console.log('Sending profile to client'); + + // Load profile, we need to operate in UTC + client.sbx.data.profile.loadData(exampleProfile); + + $('#weektoweek').addClass('selected'); + $('a.presetdates :first').click(); + $('#rp_from').val('2015-08-08'); + $('#rp_to').val('2015-09-07'); + $('#wrp_log').prop('checked', true); + $('#rp_show').click(); + + $('#wrp_linear').prop('checked', true); + $('#rp_show').click(); + $('.ui-button:contains("Save")').click(); + + var result = $('body').html(); + + result.indexOf('= new Date().getTime()) {} + var highest = ctx.notifications.findHighestAlarm('Time Ago'); highest.level.should.equal(levels.WARN); highest.message.should.equal('Last received: 16 mins ago\nBG Now: 100 mg/dl'); done(); }); - it('should trigger an urgent alarm when data older than 30m', function (done) { + it('should trigger an urgent alarm when data older than 30m', function(done) { ctx.notifications.initRequests(); - ctx.ddata.sgvs = [{mills: Date.now() - times.mins(31).msecs, mgdl: 100, type: 'sgv'}]; + ctx.ddata.sgvs = [{ mills: Date.now() - times.mins(31).msecs, mgdl: 100, type: 'sgv' }]; var sbx = freshSBX(); timeago.checkNotifications(sbx); @@ -70,55 +106,55 @@ describe('timeago', function ( ) { should.deepEqual( timeago.calcDisplay({ mills: now + times.mins(15).msecs }, now) - , {label: 'in the future', shortLabel: 'future'} + , { label: 'in the future', shortLabel: 'future' } ); //TODO: current behavior, we can do better //just a little in the future, pretend it's ok should.deepEqual( timeago.calcDisplay({ mills: now + times.mins(4).msecs }, now) - , {value: 1, label: 'min ago', shortLabel: 'm'} + , { value: 1, label: 'min ago', shortLabel: 'm' } ); should.deepEqual( timeago.calcDisplay(null, now) - , {label: 'time ago', shortLabel: 'ago'} + , { label: 'time ago', shortLabel: 'ago' } ); should.deepEqual( timeago.calcDisplay({ mills: now }, now) - , {value: 1, label: 'min ago', shortLabel: 'm'} + , { value: 1, label: 'min ago', shortLabel: 'm' } ); should.deepEqual( timeago.calcDisplay({ mills: now - 1 }, now) - , {value: 1, label: 'min ago', shortLabel: 'm'} + , { value: 1, label: 'min ago', shortLabel: 'm' } ); should.deepEqual( timeago.calcDisplay({ mills: now - times.sec(30).msecs }, now) - , {value: 1, label: 'min ago', shortLabel: 'm'} + , { value: 1, label: 'min ago', shortLabel: 'm' } ); should.deepEqual( timeago.calcDisplay({ mills: now - times.mins(30).msecs }, now) - , {value: 30, label: 'mins ago', shortLabel: 'm'} + , { value: 30, label: 'mins ago', shortLabel: 'm' } ); should.deepEqual( timeago.calcDisplay({ mills: now - times.hours(5).msecs }, now) - , {value: 5, label: 'hours ago', shortLabel: 'h'} + , { value: 5, label: 'hours ago', shortLabel: 'h' } ); should.deepEqual( timeago.calcDisplay({ mills: now - times.days(5).msecs }, now) - , {value: 5, label: 'days ago', shortLabel: 'd'} + , { value: 5, label: 'days ago', shortLabel: 'd' } ); should.deepEqual( timeago.calcDisplay({ mills: now - times.days(10).msecs }, now) - , {label: 'long ago', shortLabel: 'ago'} + , { label: 'long ago', shortLabel: 'ago' } ); }); -}); \ No newline at end of file +}); diff --git a/tests/upbat.test.js b/tests/upbat.test.js index 9b48c3b845e..42d18bb0854 100644 --- a/tests/upbat.test.js +++ b/tests/upbat.test.js @@ -93,7 +93,7 @@ describe('Uploader Battery', function ( ) { upbat.updateVisualisation(sbx); }); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var ctx = { settings: {} @@ -106,13 +106,19 @@ describe('Uploader Battery', function ( ) { var upbat = require('../lib/plugins/upbat')(ctx); upbat.setProperties(sbx); - upbat.alexa.intentHandlers.length.should.equal(1); + upbat.virtAsst.intentHandlers.length.should.equal(2); - upbat.alexa.intentHandlers[0].intentHandler(function next(title, response) { - title.should.equal('Uploader battery'); + upbat.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { + title.should.equal('Uploader Battery'); response.should.equal('Your uploader battery is at 20%'); - - done(); + + upbat.virtAsst.intentHandlers[1].intentHandler(function next(title, response) { + title.should.equal('Uploader Battery'); + response.should.equal('Your uploader battery is at 20%'); + + done(); + }, [], sbx); + }, [], sbx); }); diff --git a/tests/utils.test.js b/tests/utils.test.js index be0b298290d..53cccf07507 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -15,6 +15,22 @@ describe('utils', function ( ) { utils.toFixed(5.499999999).should.equal('5.50'); }); + it('format numbers short', function () { + var undef; + utils.toFixedMin(3.345, 2).should.equal('3.35'); + utils.toFixedMin(5.499999999, 0).should.equal('5'); + utils.toFixedMin(5.499999999, 1).should.equal('5.5'); + utils.toFixedMin(5.499999999, 3).should.equal('5.5'); + utils.toFixedMin(123.45, -2).should.equal('100'); + utils.toFixedMin(-0.001, 2).should.equal('0'); + utils.toFixedMin(-2.47, 1).should.equal('-2.5'); + utils.toFixedMin(-2.44, 1).should.equal('-2.4'); + + utils.toFixedMin(undef, 2).should.equal('0'); + utils.toFixedMin(null, 2).should.equal('0'); + utils.toFixedMin('text', 2).should.equal('0'); + }); + it('merge date and time', function () { var result = utils.mergeInputTime('22:35', '2015-07-14'); result.hours().should.equal(22); diff --git a/update.png b/update.png new file mode 100644 index 00000000000..af60ebadd54 Binary files /dev/null and b/update.png differ diff --git a/views/adminindex.html b/views/adminindex.html index 8a0f97b2196..6108d1b3978 100644 --- a/views/adminindex.html +++ b/views/adminindex.html @@ -25,12 +25,17 @@ - - - - + + + + + + <% include preloadCSS %> + +
X
+

Nightscout

@@ -45,7 +50,7 @@

Admin Tools

Authentication status: - + diff --git a/views/bgclock.html b/views/bgclock.html deleted file mode 100644 index 903a395d56a..00000000000 --- a/views/bgclock.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - Nightscout - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
-
- - - - - - - diff --git a/views/clock-color.html b/views/clock-color.html deleted file mode 100644 index 06c0fb9ba82..00000000000 --- a/views/clock-color.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - Nightscout - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
-
- - - - - - - diff --git a/views/clock.html b/views/clock.html deleted file mode 100644 index c0ac35d64be..00000000000 --- a/views/clock.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - Nightscout - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
- - - - - - - - diff --git a/views/clockviews/bgclock.css b/views/clockviews/bgclock.css new file mode 100644 index 00000000000..3e2cbef246f --- /dev/null +++ b/views/clockviews/bgclock.css @@ -0,0 +1,28 @@ +.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 new file mode 100644 index 00000000000..6a6796ef823 --- /dev/null +++ b/views/clockviews/clock-color.css @@ -0,0 +1,32 @@ +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-shared.css b/views/clockviews/clock-shared.css new file mode 100644 index 00000000000..83328fe4114 --- /dev/null +++ b/views/clockviews/clock-shared.css @@ -0,0 +1,76 @@ +body { + text-align: center; + margin: 0 0; + padding: 0; + overflow: hidden; + font-family: 'Open Sans'; + color: grey; + background-color: black; +} + +main { + 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 { + width: 100%; + -webkit-transform: translateY(-5%); +} + +#bgnow { + font-weight: 700; + font-size: 40vmin; +} + +#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; +} + +#staleTime { + flex-grow: 1; + font-size: 6vmin; + display: none; +} + +#clock { + display: none; +} + +#delta { + display: none; +} + +.close { + color: white; + font: 4em 'Open Sans'; + position: absolute; + top: 0; + right: 20px; + text-decoration: none; + z-index: 10; +} + +.close:after { + content: '\00D7'; +} + +.hidden { + opacity: 0; + transition: opacity 0.5s linear; +} \ No newline at end of file diff --git a/views/clockviews/clock.css b/views/clockviews/clock.css new file mode 100644 index 00000000000..96ffe68b84a --- /dev/null +++ b/views/clockviews/clock.css @@ -0,0 +1,5 @@ +#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/shared.html new file mode 100644 index 00000000000..cb27b137dfb --- /dev/null +++ b/views/clockviews/shared.html @@ -0,0 +1,106 @@ + + + + + + + + Nightscout + + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+
arrow
+
+
+
+
+
+ + + + + + diff --git a/views/foodindex.html b/views/foodindex.html index ea0d153958f..d42eb16a50b 100644 --- a/views/foodindex.html +++ b/views/foodindex.html @@ -24,13 +24,19 @@ - - - - - + + + + + + <% include preloadCSS %> + + +
X
+ +
Status: Not loaded @@ -119,7 +125,7 @@

Food Editor

- + diff --git a/views/index.html b/views/index.html index 6a0237f0983..b80e11ddd42 100644 --- a/views/index.html +++ b/views/index.html @@ -1,5 +1,7 @@ - + + manifest="appcache/nightscout-<%= locals.cachebuster %>.appcache" + <% } %>> @@ -27,9 +29,9 @@ - - - + + + - +<% include preloadCSS %>
@@ -116,7 +118,7 @@
-
@@ -166,18 +175,18 @@
-
+
Settings
Units
@@ -329,10 +338,20 @@ Sensor
- +
+ + + +
- + diff --git a/views/nightscout.appcache b/views/nightscout.appcache index 70716f34487..3823f894e5f 100644 --- a/views/nightscout.appcache +++ b/views/nightscout.appcache @@ -17,7 +17,8 @@ CACHE MANIFEST /manifest.json /images/favicon.ico /images/mstile-144x144.png -/css/ui-darkness/jquery-ui.min.css +/css/ui-darkness/jquery-ui.min.css?v=<%= locals.cachebuster %> +/css/jquery.tooltips.css?v=<%= locals.cachebuster %> /audio/alarm.mp3 /audio/alarm2.mp3 /css/ui-darkness/images/ui-icons_ffffff_256x240.png @@ -25,7 +26,9 @@ CACHE MANIFEST /css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png /css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png /css/main.css?v=<%= locals.cachebuster %> -/js/bundle.js?v=<%= locals.cachebuster %> +/bundle/js/bundle.app.js?v=<%= locals.cachebuster %> +/bundle/js/bundle.clock.js?v=<%= locals.cachebuster %> +/bundle/js/bundle.report.js?v=<%= locals.cachebuster %> /socket.io/socket.io.js?v=<%= locals.cachebuster %> /js/client.js?v=<%= locals.cachebuster %> /images/logo2.png diff --git a/views/preloadCSS.ejs b/views/preloadCSS.ejs new file mode 100644 index 00000000000..c4cd704b109 --- /dev/null +++ b/views/preloadCSS.ejs @@ -0,0 +1,107 @@ + + \ No newline at end of file diff --git a/views/profileindex.html b/views/profileindex.html index a0e99fc7e0f..d64f1300917 100644 --- a/views/profileindex.html +++ b/views/profileindex.html @@ -25,14 +25,18 @@ - - - - + + + + + <% include preloadCSS %> + +
X
+
-
+
Status: Not loaded

Nightscout

@@ -174,7 +178,7 @@

Profile Editor

- + diff --git a/views/reportindex.html b/views/reportindex.html index 47f1e808cda..5fa745ce34c 100644 --- a/views/reportindex.html +++ b/views/reportindex.html @@ -23,12 +23,15 @@ - - + + + <% include preloadCSS %> -

Nightscout reporting

+
X
+ +

Nightscout reporting

'+translate('Period')+''+translate('Low')+''+translate('In Range')+''+translate('High')+''+translate('Standard Deviation')+''+translate('Low Quartile')+''+translate('Average')+''+translate('Upper Quartile')+'
' + translate('Period') + '' + translate('Low') + '' + translate('In Range') + '' + translate('High') + '' + translate('Standard Deviation') + '' + translate('Low Quartile') + '' + translate('Average') + '' + translate('Upper Quartile') + '
' + v.text + '' + v.text + '' + v + '').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','50px').attr('align','left').append(translate('Protein'))) + .append($('').css('width','50px').attr('align','left').append(translate('Fat'))) .append($('').css('width','50px').attr('align','left').append(translate('Duration'))) .append($('').css('width','50px').attr('align','left').append(translate('Percent'))) .append($('').css('width','50px').attr('align','left').append(translate('Basal value'))) @@ -309,6 +327,8 @@ treatments.report = function report_treatments(datastorage, sorteddaystoshow, op .append($('').attr('align','center').append(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : '')) .append($('').attr('align','center').append(tr.insulin ? tr.insulin.toFixed(2) : '')) .append($('').attr('align','center').append(tr.carbs ? tr.carbs : '')) + .append($('').attr('align','center').append(tr.protein ? tr.protein : '')) + .append($('').attr('align','center').append(tr.fat ? tr.fat : '')) .append($('').attr('align','center').append(tr.duration ? tr.duration.toFixed(0) : '')) .append($('').attr('align','center').append(tr.percent ? tr.percent : '')) .append($('').attr('align','center').append('absolute' in tr ? tr.absolute.toFixed(2) : '')) diff --git a/lib/report_plugins/utils.js b/lib/report_plugins/utils.js index 10daec32304..be6ba940003 100644 --- a/lib/report_plugins/utils.js +++ b/lib/report_plugins/utils.js @@ -1,5 +1,7 @@ 'use strict'; +var consts = require('../constants'); + var moment = window.moment; var utils = { }; @@ -69,7 +71,7 @@ utils.scaledTreatmentBG = function scaledTreatmentBG(treatment,data) { console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + client.settings.units, treatment); if (treatment.units === 'mmol') { //BG is in mmol and display in mg/dl - treatmentGlucose = Math.round(treatment.glucose * 18); + treatmentGlucose = Math.round(treatment.glucose * consts.MMOL_TO_MGDL); } else { //BG is in mg/dl and display in mmol treatmentGlucose = client.utils.scaleMgdl(treatment.glucose); diff --git a/lib/report_plugins/weektoweek.js b/lib/report_plugins/weektoweek.js new file mode 100644 index 00000000000..8719af81542 --- /dev/null +++ b/lib/report_plugins/weektoweek.js @@ -0,0 +1,325 @@ +'use strict'; + +var _ = require('lodash'); +var moment = window.moment; +var d3 = (global && global.d3) || require('d3'); + +var dayColors = [ + 'rgb(73, 22, 153)' + , 'rgb(34, 201, 228)' + , 'rgb(0, 153, 123)' + , 'rgb(135, 135, 228)' + , 'rgb(135, 49, 204)' + , 'rgb(36, 36, 228)' + , 'rgb(0, 234, 188)' +]; + +var weektoweek = { + name: 'weektoweek' + , label: 'Week to week' + , pluginType: 'report' +}; + +function init() { + return weektoweek; +} + +module.exports = init; + +weektoweek.html = function html(client) { + var translate = client.translate; + var ret = + '

' + translate('Week to week') + '

' + + '' + translate('To see this report, press SHOW while in this view') + '
' + + ' '+translate('Size') + + ' ' + + '
' + + translate('Scale') + ': ' + + '' + + translate('Linear') + + '' + + translate('Logarithmic') + + '
' + + '
' + + '
' + ; + return ret; +}; + +weektoweek.prepareHtml = function weektoweekPrepareHtml(weekstoshow) { + $('#weektoweekcharts').html(''); + + var colorIdx = 0; + + var legend = ''; + + legend += ''; + legend += ''; + legend += ''; + legend += ''; + legend += ''; + legend += ''; + legend += ''; + legend += '
SundayMondayTuesdayWednesday
ThursdayFridaySaturday
'; + + $('#weektoweekcharts').append($(legend)); + + weekstoshow.forEach(function eachWeek(d) { + $('#weektoweekcharts').append($('
')); + }); +}; + +weektoweek.report = function report_weektoweek(datastorage, sorteddaystoshow, options) { + var Nightscout = window.Nightscout; + var client = Nightscout.client; + var report_plugins = Nightscout.report_plugins; + + var padding = { top: 15, right: 22, bottom: 30, left: 35 }; + + var weekstoshow = [ ]; + + var startDay = moment(sorteddaystoshow[0] + ' 00:00:00'); + + sorteddaystoshow.forEach( function eachDay(day) { + var weekNum = Math.abs(moment(day + ' 00:00:00').diff(startDay, 'weeks')); + + if (typeof weekstoshow[weekNum] === 'undefined') { + weekstoshow[weekNum] = [ ]; + } + + weekstoshow[weekNum].push(day); + }); + + weekstoshow = weekstoshow.map(function orderWeek(week) { + return _.sortBy(week); + }); + + weektoweek.prepareHtml(weekstoshow); + + weekstoshow.forEach( function eachWeek(week) { + var sgvData = [ ]; + var weekStart = moment(week[0] + ' 00:00:00'); + + week.forEach( function eachDay(day) { + var dayNum = Math.abs(moment(day + ' 00:00:00').diff(weekStart, 'days')); + + datastorage[day].sgv.forEach ( function eachSgv(sgv) { + var sgvWeekday = moment(sgv.date).day(); + var sgvColor = dayColors[sgvWeekday]; + + if (sgv.color === 'gray') { + sgvColor = sgv.color; + } + + sgvData.push( { + 'color': sgvColor + , 'date': moment(sgv.date).subtract(dayNum, 'days').toDate() + , 'filtered': sgv.filtered + , 'mills': sgv.mills - dayNum * 24*60*60000 + , 'noise': sgv.noise + , 'sgv': sgv.sgv + , 'type': sgv.type + , 'unfiltered': sgv.unfiltered + , 'y': sgv.y + }); + }); + }); + + drawChart(week, sgvData, options); + }); + + function timeTicks(n, i) { + var t12 = [ + '12am', '', '2am', '', '4am', '', '6am', '', '8am', '', '10am', '', + '12pm', '', '2pm', '', '4pm', '', '6pm', '', '8pm', '', '10pm', '', '12am' + ]; + if (Nightscout.client.settings.timeFormat === 24) { + return ('00' + i).slice(-2); + } else { + return t12[i]; + } + } + + function drawChart(week, sgvData, options) { + var tickValues + , charts + , context + , xScale2, yScale2 + , xAxis2, yAxis2 + , dateFn = function (d) { return new Date(d.date); }; + + tickValues = client.ticks(client, { + scaleY: options.weekscale === report_plugins.consts.SCALE_LOG ? 'log' : 'linear' + , targetTop: options.targetHigh + , targetBottom: options.targetLow + }); + + // add defs for combo boluses + var dashWidth = 5; + d3.select('body').append('svg') + .append('defs') + .append('pattern') + .attr('id', 'hash') + .attr('patternUnits', 'userSpaceOnUse') + .attr('width', 6) + .attr('height', 6) + .attr('x', 0) + .attr('y', 0) + .append('g') + .style('fill', 'none') + .style('stroke', '#0099ff') + .style('stroke-width', 2) + .append('path').attr('d', 'M0,0 l' + dashWidth + ',' + dashWidth) + .append('path').attr('d', 'M' + dashWidth + ',0 l-' + dashWidth + ',' + dashWidth); + + // create svg and g to contain the chart contents + charts = d3.select('#weektoweekchart-' + week[0] + '-' + week[week.length-1]).html( + ''+ + report_plugins.utils.localeDate(week[0])+ + '-' + + report_plugins.utils.localeDate(week[week.length-1])+ + '
' + ).append('svg'); + + charts.append('rect') + .attr('width', '100%') + .attr('height', '100%') + .attr('fill', 'WhiteSmoke'); + + context = charts.append('g'); + + // define the parts of the axis that aren't dependent on width or height + xScale2 = d3.scaleTime() + .domain(d3.extent(sgvData, dateFn)); + + if (options.weekscale === report_plugins.consts.SCALE_LOG) { + yScale2 = d3.scaleLog() + .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); + } else { + yScale2 = d3.scaleLinear() + .domain([client.utils.scaleMgdl(36), client.utils.scaleMgdl(420)]); + } + + xAxis2 = d3.axisBottom(xScale2) + .tickFormat(timeTicks) + .ticks(24); + + yAxis2 = d3.axisLeft(yScale2) + .tickFormat(d3.format('d')) + .tickValues(tickValues); + + // get current data range + var dataRange = d3.extent(sgvData, dateFn); + + // get the entire container height and width subtracting the padding + var chartWidth = options.weekwidth - padding.left - padding.right; + var chartHeight = options.weekheight - padding.top - padding.bottom; + + //set the width and height of the SVG element + charts.attr('width', options.weekwidth) + .attr('height', options.weekheight); + + // ranges are based on the width and height available so reset + xScale2.range([0, chartWidth]); + yScale2.range([chartHeight,0]); + + // add target BG rect + context.append('rect') + .attr('x', xScale2(dataRange[0])+padding.left) + .attr('y', yScale2(options.targetHigh)+padding.top) + .attr('width', xScale2(dataRange[1]- xScale2(dataRange[0]))) + .attr('height', yScale2(options.targetLow)-yScale2(options.targetHigh)) + .style('fill', '#D6FFD6') + .attr('stroke', 'grey'); + + // create the x axis container + context.append('g') + .attr('class', 'x axis'); + + // create the y axis container + context.append('g') + .attr('class', 'y axis'); + + context.select('.y') + .attr('transform', 'translate(' + (padding.left) + ',' + padding.top + ')') + .style('stroke', 'black') + .style('shape-rendering', 'crispEdges') + .style('fill', 'none') + .call(yAxis2); + + // if first run then just display axis with no transition + context.select('.x') + .attr('transform', 'translate(' + padding.left + ',' + (chartHeight + padding.top) + ')') + .style('stroke', 'black') + .style('shape-rendering', 'crispEdges') + .style('fill', 'none') + .call(xAxis2); + + _.each(tickValues, function (n, li) { + context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale2(dataRange[0])+padding.left) + .attr('y1', yScale2(tickValues[li])+padding.top) + .attr('x2', xScale2(dataRange[1])+padding.left) + .attr('y2', yScale2(tickValues[li])+padding.top) + .style('stroke-dasharray', ('1, 5')) + .attr('stroke', 'grey'); + }); + + // bind up the context chart data to an array of circles + var contextCircles = context.selectAll('circle') + .data(sgvData); + + function prepareContextCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { + return xScale2(d.date) + padding.left; + }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + badData.push(d); + return yScale2(client.utils.scaleMgdl(450) + padding.top); + } else { + return yScale2(d.sgv) + padding.top; + } + }) + .attr('fill', function (d) { + if (d.color === 'gray') { + return 'transparent'; + } + return d.color; + }) + .style('opacity', function () { return 0.5 }) + .attr('stroke-width', function (d) {if (d.type === 'mbg') { return 2; } else { return 0; }}) + .attr('stroke', function () { return 'black'; }) + .attr('r', function(d) { + if (d.type === 'mbg') { + return 4; + } else { + return 2 + (options.weekwidth - 800) / 400; + } + }) + .on('mouseout', hideTooltip); + + if (badData.length > 0) { + console.warn('Bad Data: isNaN(sgv)', badData); + } + return sel; + } + + // if new circle then just display + prepareContextCircles(contextCircles.enter().append('circle')); + + contextCircles.exit() + .remove(); + } + + function hideTooltip ( ) { + client.tooltip.style('opacity', 0); + } +}; diff --git a/lib/sandbox.js b/lib/sandbox.js index 3379bf509ea..4bcee3b40e4 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -4,21 +4,21 @@ var _ = require('lodash'); var units = require('./units')(); var times = require('./times'); -function init ( ) { +function init () { var sbx = {}; - function reset () { - sbx.properties = { }; + function reset () { + sbx.properties = {}; } - function extend ( ) { + function extend () { sbx.unitsLabel = unitsLabel(); sbx.data = sbx.data || {}; //default to prevent adding checks everywhere - sbx.extendedSettings = {empty: true}; + sbx.extendedSettings = { empty: true }; } - function withExtendedSettings(plugin, allExtendedSettings, sbx) { + function withExtendedSettings (plugin, allExtendedSettings, sbx) { var sbx2 = _.extend({}, sbx); sbx2.extendedSettings = allExtendedSettings && allExtendedSettings[plugin.name] || {}; return sbx2; @@ -44,11 +44,12 @@ function init ( ) { sbx.serverInit = function serverInit (env, ctx) { reset(); + sbx.runtimeEnvironment = 'server'; sbx.time = Date.now(); sbx.settings = env.settings; sbx.data = ctx.ddata.clone(); sbx.notifications = safeNotifications(ctx); - + sbx.levels = ctx.levels; sbx.language = ctx.language; sbx.translate = ctx.language.translate; @@ -60,7 +61,7 @@ function init ( ) { sbx.data.profile = profile; delete sbx.data.profiles; - sbx.properties = { }; + sbx.properties = {}; sbx.withExtendedSettings = function getPluginExtendedSettingsOnly (plugin) { return withExtendedSettings(plugin, env.extendedSettings, sbx); @@ -83,23 +84,24 @@ function init ( ) { sbx.clientInit = function clientInit (ctx, time, data) { reset(); + sbx.runtimeEnvironment = 'client'; sbx.settings = ctx.settings; sbx.showPlugins = ctx.settings.showPlugins; sbx.time = time; sbx.data = data; sbx.pluginBase = ctx.pluginBase; sbx.notifications = safeNotifications(ctx); - + sbx.levels = ctx.levels; sbx.language = ctx.language; sbx.translate = ctx.language.translate; if (sbx.pluginBase) { sbx.pluginBase.forecastInfos = []; - sbx.pluginBase.forecastPoints = []; + sbx.pluginBase.forecastPoints = {}; } - sbx.extendedSettings = {empty: true}; + sbx.extendedSettings = { empty: true }; sbx.withExtendedSettings = function getPluginExtendedSettingsOnly (plugin) { return withExtendedSettings(plugin, sbx.settings.extendedSettings, sbx); }; @@ -116,7 +118,7 @@ function init ( ) { * @param setter */ sbx.offerProperty = function offerProperty (name, setter) { - if (!sbx.properties.hasOwnProperty(name)) { + if (!Object.keys(sbx.properties).includes(name)) { var value = setter(); if (value) { sbx.properties[name] = value; @@ -124,7 +126,7 @@ function init ( ) { } }; - sbx.isCurrent = function isCurrent(entry) { + sbx.isCurrent = function isCurrent (entry) { return entry && sbx.time - entry.mills <= times.mins(15).msecs; }; @@ -137,7 +139,7 @@ function init ( ) { sbx.lastNEntries = function lastNEntries (entries, n) { var lastN = []; - _.takeRightWhile(entries, function (entry) { + _.takeRightWhile(entries, function(entry) { if (sbx.entryMills(entry) <= sbx.time) { lastN.push(entry); } @@ -158,32 +160,32 @@ function init ( ) { return sbx.prevEntry(sbx.data.sgvs); }; - sbx.lastSGVEntry = function lastSGVEntry ( ) { + sbx.lastSGVEntry = function lastSGVEntry () { return sbx.lastEntry(sbx.data.sgvs); }; - sbx.lastSGVMgdl = function lastSGVMgdl ( ) { + sbx.lastSGVMgdl = function lastSGVMgdl () { var last = sbx.lastSGVEntry(); return last && last.mgdl; }; - sbx.lastSGVMills = function lastSGVMills ( ) { + sbx.lastSGVMills = function lastSGVMills () { return sbx.entryMills(sbx.lastSGVEntry()); }; - sbx.entryMills = function entryMills(entry) { + sbx.entryMills = function entryMills (entry) { return entry && entry.mills; }; - sbx.lastScaledSGV = function lastScaledSVG ( ) { + sbx.lastScaledSGV = function lastScaledSVG () { return sbx.scaleEntry(sbx.lastSGVEntry()); }; - sbx.lastDisplaySVG = function lastDisplaySVG ( ) { + sbx.lastDisplaySVG = function lastDisplaySVG () { return sbx.displayBg(sbx.lastSGVEntry()); }; - sbx.buildBGNowLine = function buildBGNowLine ( ) { + sbx.buildBGNowLine = function buildBGNowLine () { var line = 'BG Now: ' + sbx.lastDisplaySVG(); var delta = sbx.properties.delta && sbx.properties.delta.display; @@ -216,7 +218,7 @@ function init ( ) { return lines; }; - sbx.prepareDefaultLines = function prepareDefaultLines() { + sbx.prepareDefaultLines = function prepareDefaultLines () { var lines = [sbx.buildBGNowLine()]; sbx.appendPropertyLine('rawbg', lines); sbx.appendPropertyLine('ar2', lines); @@ -227,7 +229,7 @@ function init ( ) { return lines; }; - sbx.buildDefaultMessage = function buildDefaultMessage() { + sbx.buildDefaultMessage = function buildDefaultMessage () { return sbx.prepareDefaultLines().join('\n'); }; @@ -272,14 +274,10 @@ function init ( ) { if (sbx.properties.roundingStyle === 'medtronic') { var denominator = 0.1; var digits = 1; - if (insulin > 0.5 && iob < 1) { + if (insulin <= 0.5) { denominator = 0.05; digits = 2; } - if (insulin <= 0.5) { - denominator = 0.025; - digits = 3; - } return (Math.floor(insulin / denominator) * denominator).toFixed(digits); } @@ -287,7 +285,7 @@ function init ( ) { }; - function unitsLabel ( ) { + function unitsLabel () { return sbx.settings.units === 'mmol' ? 'mmol/L' : 'mg/dl'; } @@ -299,4 +297,3 @@ function init ( ) { } module.exports = init; - diff --git a/lib/server/activity.js b/lib/server/activity.js index f7df9f48268..45b77e60587 100644 --- a/lib/server/activity.js +++ b/lib/server/activity.js @@ -9,6 +9,11 @@ function storage (env, ctx) { function create (obj, fn) { obj.created_at = (new Date( )).toISOString( ); api().insert(obj, function (err, doc) { + if (err != null && err.message) { + console.log('Activity data insertion error', err.message); + fn(err.message, null); + return; + } fn(null, doc.ops); }); } @@ -56,7 +61,8 @@ function storage (env, ctx) { } function remove (_id, fn) { - return api( ).remove({ '_id': new ObjectID(_id) }, fn); + var objId = new ObjectID(_id); + return api( ).remove({ '_id': objId }, fn); } function api ( ) { diff --git a/lib/server/bootevent.js b/lib/server/bootevent.js index f1580932ecd..2bb63ad78f0 100644 --- a/lib/server/bootevent.js +++ b/lib/server/bootevent.js @@ -2,10 +2,53 @@ var _ = require('lodash'); -var UPDATE_THROTTLE = 1000; +var UPDATE_THROTTLE = 5000; function boot (env, language) { + ////////////////////////////////////////////////// + // Check Node version. + // Latest Node 8 LTS and Latest Node 10 LTS are recommended and supported. + // Latest Node version on Azure is tolerated, but not recommended + // Latest Node (non LTS) version works, but is not recommended + // Older Node versions or Node versions with known security issues will not work. + // More explicit: + // < 8 does not work, not supported + // >= 8.15.1 works, supported and recommended + // == 9.x does not work, not supported + // == 10.15.2 works, not fully supported and not recommended (Azure version) + // >= 10.16.0 works, supported and recommended + // == 11.x does not work, not supported + // >= 12.6.0 does work, not recommended, will not be supported. We only support Node LTS releases + /////////////////////////////////////////////////// + function checkNodeVersion (ctx, next) { + var semver = require('semver'); + var nodeVersion = process.version; + + if ( semver.satisfies(nodeVersion, '^8.15.1') || semver.satisfies(nodeVersion, '^10.16.0')) { + //Latest Node 8 LTS and Latest Node 10 LTS are recommended and supported. + //Require at least Node 8 LTS and Node 10 LTS without known security issues + console.debug('Node LTS version ' + nodeVersion + ' is supported'); + next(); + } + else if ( semver.eq(nodeVersion, '10.15.2')) { + //Latest Node version on Azure is tolerated, but not recommended + console.log('WARNING: Node version v10.15.2 and Microsoft Azure are not recommended.'); + console.log('WARNING: Please migrate to another hosting provider. Your Node version is outdated and insecure'); + next(); + } + else if ( semver.satisfies(nodeVersion, '^12.6.0')) { + //Latest Node version + console.debug('Node version ' + nodeVersion + ' is not a LTS version. Not recommended. Not supported'); + next(); + } else { + // Other versions will not start + console.log( 'ERROR: Node version ' + nodeVersion + ' is not supported. Please use a secure LTS version or upgrade your Node'); + process.exit(1); + } + } + + function checkEnv (ctx, next) { ctx.language = language; if (env.err) { @@ -26,6 +69,7 @@ function boot (env, language) { try { href = url.parse(configURL).href; } catch (e) { + console.error('Parsing config URL from IMPORT_CONFIG failed'); } if(configURL && href) { var request = require('request'); @@ -71,7 +115,7 @@ function boot (env, language) { } else { //TODO assume mongo for now, when there are more storage options add a lookup require('../storage/mongo-storage')(env, function ready(err, store) { - // FIXME, error is always null, if there is an error, the storage.js will throw an exception + // FIXME, error is always null, if there is an error, the index.js will throw an exception console.log('Mongo Storage system ready'); ctx.store = store; @@ -105,7 +149,7 @@ function boot (env, language) { if (hasBootErrors(ctx)) { return next(); } - + ctx.levels = require('../levels'); ctx.levels.translate = ctx.language.translate; @@ -120,6 +164,7 @@ function boot (env, language) { ctx.pushover = require('../plugins/pushover')(env); ctx.maker = require('../plugins/maker')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); + ctx.loop = require('./loop')(env, ctx); ctx.activity = require('./activity')(env, ctx); ctx.entries = require('./entries')(env, ctx); @@ -138,6 +183,10 @@ function boot (env, language) { ctx.alexa = require('../plugins/alexa')(env, ctx); } + if (env.settings.isEnabled('googlehome')) { + ctx.googleHome = require('../plugins/googlehome')(env, ctx); + } + next( ); } @@ -179,6 +228,7 @@ function boot (env, language) { }); ctx.bus.on('data-loaded', function updatePlugins ( ) { + console.info('reloading sandbox data'); var sbx = require('../sandbox')().serverInit(env, ctx); ctx.plugins.setProperties(sbx); ctx.notifications.initRequests(); @@ -227,6 +277,7 @@ function boot (env, language) { } return require('bootevent')( ) + .acquire(checkNodeVersion) .acquire(checkEnv) .acquire(augmentSettings) .acquire(setupStorage) diff --git a/lib/server/clocks.js b/lib/server/clocks.js new file mode 100644 index 00000000000..282acd01951 --- /dev/null +++ b/lib/server/clocks.js @@ -0,0 +1,35 @@ +'use strict'; + +const express = require('express'); +const path = require('path'); + +// eslint-disable-next-line no-unused-vars +function clockviews(env, ctx) { + + const app = new express(); + let locals = {}; + + app.set('view engine', 'ejs'); + app.engine('html', require('ejs').renderFile); + app.set("views", path.join(__dirname, "../../views/clockviews/")); + + app.get('/:face', (req, res) => { + + const face = req.params.face; + console.log('Clockface requested:', face); + + res.render('shared.html', { + face, + locals + }); + + }); + + app.setLocals = function (_locals) { + locals = _locals; + } + + return app; +} + +module.exports = clockviews; \ No newline at end of file diff --git a/lib/server/devicestatus.js b/lib/server/devicestatus.js index 1939450adb5..d35c6be87cb 100644 --- a/lib/server/devicestatus.js +++ b/lib/server/devicestatus.js @@ -1,15 +1,23 @@ 'use strict'; +var moment = require('moment'); var find_options = require('./query'); function storage (collection, ctx) { - var ObjectID = require('mongodb').ObjectID; function create(obj, fn) { - if (! obj.hasOwnProperty('created_at')){ - obj.created_at = (new Date()).toISOString(); - } + + // Normalize all dates to UTC + const d = moment(obj.created_at).isValid() ? moment.parseZone(obj.created_at) : moment(); + obj.created_at = d.toISOString(); + obj.utcOffset = d.utcOffset(); + api().insert(obj, function (err, doc) { + if (err != null && err.message) { + console.log('Error inserting the device status object', err.message); + fn(err.message, null); + return; + } fn(null, doc.ops); ctx.bus.emit('data-received'); }); @@ -59,14 +67,8 @@ function storage (collection, ctx) { ).toArray(toArray); } - function remove (_id, fn) { - var filter; - if (_id === '*') { - filter = {}; - } else { - filter = { '_id': new ObjectID(_id) }; - } - return api( ).remove(filter, fn); + function remove (opts, fn) { + return api( ).remove(query_for(opts), fn); } function api() { diff --git a/lib/server/entries.js b/lib/server/entries.js index 72cd009f607..d7258f13b79 100644 --- a/lib/server/entries.js +++ b/lib/server/entries.js @@ -1,9 +1,9 @@ 'use strict'; var es = require('event-stream'); -var sgvdata = require('sgvdata'); var find_options = require('./query'); var ObjectID = require('mongodb').ObjectID; +var moment = require('moment'); /**********\ * Entries @@ -46,7 +46,11 @@ function storage(env, ctx) { } function remove (opts, fn) { - api( ).remove(query_for(opts), fn); + api( ).remove(query_for(opts), function (err, stat) { + //TODO: this is triggering a read from Mongo, we can do better + ctx.bus.emit('data-received'); + fn(err, stat); + }); } // return writable stream to lint each sgv record passing through it @@ -75,8 +79,6 @@ function storage(env, ctx) { //function update (fn) { //} // - //function remove (fn) { - //} // store new documents using the storage mechanism function create (docs, fn) { @@ -86,6 +88,16 @@ function storage(env, ctx) { totalCreated = 0; docs.forEach(function(doc) { + + // Normalize dates to be in UTC, store offset in utcOffset + + var _sysTime = moment(doc.dateString).isValid() ? moment.parseZone(doc.dateString) : moment(doc.date); + _sysTime = _sysTime.isValid() ? _sysTime : moment(); + + doc.utcOffset = _sysTime.utcOffset(); + doc.sysTime = _sysTime.toISOString(); + if (doc.dateString) doc.dateString = doc.sysTime; + var query = (doc.sysTime && doc.type) ? {sysTime: doc.sysTime, type: doc.type} : doc; api( ).update(query, doc, {upsert: true}, function (err) { firstErr = firstErr || err; @@ -121,7 +133,6 @@ function storage(env, ctx) { // Expose all the useful functions api.list = list; - api.echo = sgvdata.sync.json.echo; api.map = map; api.create = create; api.remove = remove; diff --git a/lib/server/food.js b/lib/server/food.js index 18a293690bf..92c41843f7b 100644 --- a/lib/server/food.js +++ b/lib/server/food.js @@ -6,6 +6,11 @@ function storage (env, ctx) { function create (obj, fn) { obj.created_at = (new Date( )).toISOString( ); api().insert(obj, function (err, doc) { + if (err != null && err.message) { + console.log('Data insertion error', err.message); + fn(err.message, null); + return; + } fn(null, doc.ops); }); } @@ -36,7 +41,8 @@ function storage (env, ctx) { } function remove (_id, fn) { - return api( ).remove({ '_id': new ObjectID(_id) }, fn); + var objId = new ObjectID(_id); + return api( ).remove({ '_id': objId }, fn); } diff --git a/lib/server/loop.js b/lib/server/loop.js new file mode 100644 index 00000000000..6a04fcd73aa --- /dev/null +++ b/lib/server/loop.js @@ -0,0 +1,109 @@ +'use strict'; + +const apn = require('apn'); + +function init (env, ctx) { + + function loop () { + return loop; + } + + loop.sendNotification = function sendNotification (data, remoteAddress, completion) { + if (env.extendedSettings.loop.apnsKey === undefined || env.extendedSettings.loop.apnsKey.length == 0) { + completion("Loop notification failed: LOOP_APNS_KEY not set."); + return; + } + + if (env.extendedSettings.loop.apnsKeyId === undefined || env.extendedSettings.loop.apnsKeyId.length == 0) { + completion("Loop notification failed: LOOP_APNS_KEY_ID not set."); + return; + } + + if (env.extendedSettings.loop.developerTeamId === undefined || env.extendedSettings.loop.developerTeamId.length != 10) { + completion("Loop notification failed: LOOP_DEVELOPER_TEAM_ID not set."); + return; + } + + if (env.extendedSettings.loop.developerTeamId === undefined || env.extendedSettings.loop.developerTeamId.length != 10) { + completion("Loop notification failed: LOOP_DEVELOPER_TEAM_ID not set."); + return; + } + + if (ctx.ddata.profiles === undefined || ctx.ddata.profiles.length < 1 || ctx.ddata.profiles[0].loopSettings === undefined) { + completion("Loop notification failed: Could not find loopSettings in profile."); + return; + } + + let loopSettings = ctx.ddata.profiles[0].loopSettings; + + if (loopSettings.deviceToken === undefined) { + completion("Loop notification failed: Could not find deviceToken in loopSettings."); + return; + } + + if (loopSettings.bundleIdentifier === undefined) { + completion("Loop notification failed: Could not find bundleIdentifier in loopSettings."); + return; + } + + var options = { + token: { + key: env.extendedSettings.loop.apnsKey + , keyId: env.extendedSettings.loop.apnsKeyId + , teamId: env.extendedSettings.loop.developerTeamId + }, + production: env.extendedSettings.loop.pushServerEnvironment === "production" + }; + + var provider = new apn.Provider(options); + + var payload = { + 'remote-address': remoteAddress, + 'notes': data.notes, + 'entered-by': data.enteredBy + }; + var alert; + if (data.eventType === 'Temporary Override Cancel') { + payload["cancel-temporary-override"] = "true"; + alert = "Cancel Temporary Override"; + } else if (data.eventType === 'Temporary Override') { + payload["override-name"] = data.reason; + alert = data.reasonDisplay + " Temporary Override"; + } else { + completion("Loop notification failed: Unhandled event type:", data.eventType); + return; + } + + if (data.notes !== undefined && data.notes.length > 0) { + alert += " - " + data.notes + } + + if (data.enteredBy !== undefined && data.enteredBy.length > 0) { + alert += " - " + data.enteredBy + } + + let notification = new apn.Notification(); + notification.alert = alert; + notification.topic = loopSettings.bundleIdentifier; + notification.contentAvailable = 1; + 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(); + } else { + console.log("APNs delivery failed:", response.failed) + completion("APNs delivery failed: " + response.failed[0].response.reason); + } + }); + }; + + return loop(); +} + +module.exports = init; diff --git a/lib/server/mqtt.js b/lib/server/mqtt.js deleted file mode 100644 index 0a4058e1a8e..00000000000 --- a/lib/server/mqtt.js +++ /dev/null @@ -1,317 +0,0 @@ -'use strict'; - -var es = require('event-stream'); -var Long = require('long'); -var decoders = require('sgvdata/lib/protobuf'); -var direction = require('sgvdata/lib/utils').direction; -var moment = require('moment'); -var url = require('url'); - -function init (env, ctx) { - - function mqtt ( ) { - return mqtt; - } - var info = url.parse(env.MQTT_MONITOR); - var username = info.auth.split(':').slice(0, -1).join(''); - var shared_topic = '/downloads/' + username + '/#'; - var alias_topic = '/downloads/' + username + '/protobuf'; - var notification_topic = '/notifications/' + username + '/json'; - env.mqtt_shared_topic = shared_topic; - - mqtt.client = connect(env); - var downloads = mqtt.downloads = downloader(); - - if (mqtt.client) { - listenForMessages(ctx); - } - - mqtt.every = every; - mqtt.entries = process(); - - //expose for tests that don't need to connect - mqtt.sgvSensorMerge = sgvSensorMerge; - - function listenForMessages ( ) { - mqtt.client.on('message', function (topic, msg) { - console.log('topic', topic); - // XXX: ugly hack - if (topic === alias_topic) { - topic = '/downloads/protobuf'; - } - - console.log(topic, 'on message', 'msg', msg.length); - switch (topic) { - case '/uploader': - console.log({type: topic, msg: msg.toString()}); - break; - case '/downloads/protobuf': - downloadProtobuf(msg, topic, downloads, ctx); - break; - - default: - console.log(topic, 'on message', 'msg', msg); - // ctx.entries.write(msg); - break; - } - }); - } - - mqtt.emitNotification = function emitNotification(notify) { - console.info('Publishing notification to mqtt: ', notify); - [notification_topic, '/notifications/json'].forEach(function iter_notify (topic) { - mqtt.client.publish(topic, JSON.stringify(notify), function mqttCallback (err) { - if (err) { - console.error('Unable to publish notification to MQTT', err); - } - }); - }); - }; - - return mqtt(); -} - -function connect (env) { - var uri = env.MQTT_MONITOR; - var shared_topic = env.mqtt_shared_topic; - if (!uri) { - return null; - } - - var opts = { - encoding: 'binary', - clean: false, - clientId: env.mqtt_client_id - }; - var client = require('mqtt').connect(uri, opts); - - function granted () { console.log('granted', arguments); } - - client.subscribe('sgvs'); - client.subscribe('published'); - client.subscribe('/downloads/protobuf', {qos: 2}, granted); - client.subscribe(shared_topic, {qos: 2}, granted); - client.subscribe('/uploader', granted); - client.subscribe('/entries/sgv', granted); - - return client; -} - -function process ( ) { - var stream = es.through( - function _write(data) { - this.push(data); - } - ); - return stream; -} - -function every (storage) { - function iter(item, next) { - storage.create(item, next); - } - - return es.map(iter); -} - -function downloader ( ) { - var opts = { - model: decoders.models.G4Download - , json: function (o) { - return o; - } - , payload: function (o) { - return o; - } - }; - return decoders(opts); -} - -function downloadProtobuf (msg, topic, downloads, ctx) { - var b = new Buffer(msg, 'binary'); - console.log('BINARY', b.length, b.toString('hex')); - var packet; - try { - packet = downloads.parse(b); - if (!packet.type) { - packet.type = topic; - - } - console.log('DOWNLOAD msg', msg.length, packet); - console.log('download SGV', packet.sgv[0]); - console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); - console.log('WRITE TO MONGO'); - var download_timestamp = moment(packet.download_timestamp); - if (packet.download_status === 0) { - es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING MERGED SGV TO MONGO', err, result); - })); - - iter_mqtt_record_stream(packet, 'cal', toCal) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING Cal TO MONGO', err, result.length); - })); - iter_mqtt_record_stream(packet, 'meter', toMeter) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING Meter TO MONGO', err, result.length); - })); - } - packet.type = 'download'; - ctx.devicestatus.create({ - uploaderBattery: packet.uploader_battery, - created_at: download_timestamp.toISOString() - }, function empty(err, result) { - console.log('DONE WRITING TO MONGO devicestatus ', result, err); - }); - - ctx.entries.create([ packet ], function empty(err) { - if (err) { - console.log('Error writting to mongo: ', err); - } else { - console.log('Download written to mongo: ', packet); - } - }); - } catch (e) { - console.log('DID NOT PARSE', e); - } -} - -function toSGV (proto, vars) { - vars.sgv = proto.sgv_mgdl; - vars.direction = direction(proto.trend); - vars.noise = proto.noise; - vars.type = 'sgv'; - return vars; -} - -function toCal (proto, vars) { - vars.slope = proto.slope; - vars.intercept = proto.intercept; - vars.scale = proto.scale; - vars.type = 'cal'; - return vars; -} - -function toSensor (proto, vars) { - vars.filtered = new Long(proto.filtered).toInt(); - vars.unfiltered = new Long(proto.unfiltered).toInt(); - vars.rssi = proto.rssi; - vars.type = 'sensor'; - return vars; -} - -function toMeter (proto, result) { - result.type = 'mbg'; - result.mbg = proto.mbg || proto.meter_bg_mgdl; - return result; -} - -function toTimestamp (proto, receiver_time, download_time) { - var record_offset = receiver_time - proto.sys_timestamp_sec; - var record_time = download_time.clone( ).subtract(record_offset, 'second'); - var obj = { - device: 'dexcom' - , date: record_time.unix() * 1000 - , dateString: record_time.format( ) - }; - return obj; -} - -function timestampFactory (packet) { - var receiver_time = packet.receiver_system_time_sec; - var download_time = moment(packet.download_timestamp); - function timestamp (item) { - return toTimestamp(item, receiver_time, download_time.clone( )); - } - return timestamp; -} - -function timeSort (a, b) { - return a.date - b.date; -} - -function sgvSensorMerge (packet) { - var timestamp = timestampFactory(packet); - var sgvs = (packet['sgv'] || []).map(function(sgv) { - var timestamped = timestamp(sgv); - return toSGV(sgv, timestamped); - }).sort(timeSort); - - var sensors = (packet['sensor'] || []).map(function(sensor) { - var timestamped = timestamp(sensor); - return toSensor(sensor, timestamped); - }).sort(timeSort); - - //based on com.nightscout.core.dexcom.Utils#mergeGlucoseDataRecords - var merged = [] - , sgvsLength = sgvs.length - , sensorsLength = sensors.length; - - if (sgvsLength >= 0 && sensorsLength === 0) { - merged = sgvs; - } else { - var smallerLength = Math.min(sgvsLength, sensorsLength); - for (var i = 1; i <= smallerLength; i++) { - var sgv = sgvs[sgvsLength - i]; - var sensor = sensors[sensorsLength - i]; - if (sgv && sensor && Math.abs(sgv.date - sensor.date) < 10000) { - //timestamps are close so merge - sgv.filtered = sensor.filtered; - sgv.unfiltered = sensor.unfiltered; - sgv.rssi = sensor.rssi; - merged.push(sgv); - } else { - console.info('mismatch or missing, sgv: ', sgv, ' sensor: ', sensor); - //timestamps aren't close enough so add both - if (sgv) { merged.push(sgv); } - //but the sensor will become and sgv now - if (sensor) { - sensor.type = 'sgv'; - merged.push(sensor); - } - } - } - - //any extra sgvs? - if (sgvsLength > smallerLength) { - for (var j = 0; j < sgvsLength - smallerLength; j++) { - var extraSGV = sgvs[j]; - merged.push(extraSGV); - } - } - - //any extra sensors? - if (sensorsLength > smallerLength) { - for (var k = 0; k < sensorsLength - smallerLength; k++) { - var extraSensor = sensors[k]; - //from now on we consider it a sgv - extraSensor.type = 'sgv'; - merged.push(extraSensor); - } - } - - } - - return merged; -} - -function iter_mqtt_record_stream (packet, prop, sync) { - var list = packet[prop]; - console.log('incoming', prop, (list || [ ]).length); - var stream = es.readArray(list || [ ]); - var receiver_time = packet.receiver_system_time_sec; - var download_time = moment(packet.download_timestamp); - function map(item, next) { - var timestamped = toTimestamp(item, receiver_time, download_time.clone( )); - var r = sync(item, timestamped); - if (!('type' in r)) { - r.type = prop; - } - console.log('ITEM', item, 'TO', prop, r); - next(null, r); - } - return stream.pipe(es.map(map)); -} - -init.downloadProtobuf = downloadProtobuf; -module.exports = init; diff --git a/lib/server/profile.js b/lib/server/profile.js index 110f7b64e87..d456b590959 100644 --- a/lib/server/profile.js +++ b/lib/server/profile.js @@ -32,7 +32,8 @@ function storage (collection, ctx) { } function remove (_id, fn) { - api( ).remove({ '_id': new ObjectID(_id) }, fn); + var objId = new ObjectID(_id); + api( ).remove({ '_id': objId }, fn); ctx.bus.emit('data-received'); } diff --git a/lib/server/pushnotify.js b/lib/server/pushnotify.js index 49748851659..48d5be11078 100644 --- a/lib/server/pushnotify.js +++ b/lib/server/pushnotify.js @@ -1,15 +1,15 @@ 'use strict'; -var _ = require('lodash'); -var crypto = require('crypto'); -var NodeCache = require('node-cache'); +const _ = require('lodash'); +const crypto = require('crypto'); +const NodeCache = require('node-cache'); -var levels = require('../levels'); -var times = require('../times'); +const levels = require('../levels'); +const times = require('../times'); -function init(env, ctx) { +function init (env, ctx) { - function pushnotify() { + function pushnotify () { return pushnotify; } @@ -23,17 +23,20 @@ function init(env, ctx) { return; } - var key = null; - if (notify.isAnnouncement) { - //Announcement notifications are sent if they are different from whats been recently sent - key = notifyToHash(notify); - } else if (levels.isAlarm(notify.level)) { - //Alarms can be snoozed - //for WARN and higher use the plugin name and notification level so that louder alarms aren't triggered too often - key = notify.plugin.name + '_' + notify.level; - } else { - //INFO and lower notifications should be sent as long as they are different from whats been recently sent - key = notifyToHash(notify); + var key = notify.notifyhash || false; + + if (!key) { + if (notify.isAnnouncement) { + //Announcement notifications are sent if they are different from whats been recently sent + key = notifyToHash(notify); + } else if (levels.isAlarm(notify.level)) { + //Alarms can be snoozed + //for WARN and higher use the plugin name and notification level so that louder alarms aren't triggered too often + key = notify.plugin.name + '_' + notify.level; + } else { + //INFO and lower notifications should be sent as long as they are different from whats been recently sent + key = notifyToHash(notify); + } } notify.key = key; @@ -69,12 +72,12 @@ function init(env, ctx) { return !!notify; }; - function cancelPushoverNotifications ( ) { + function cancelPushoverNotifications () { if (ctx.pushover) { var receiptKeys = receipts.keys(); - _.each(receiptKeys, function eachKey(receipt) { - ctx.pushover.cancelWithReceipt(receipt, function cancelCallback(err) { + _.each(receiptKeys, function eachKey (receipt) { + ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err) { if (err) { console.error('error canceling receipt:' + receipt + ', err: ', err); } else { @@ -89,7 +92,7 @@ function init(env, ctx) { function sendPushoverNotifications (notify) { if (ctx.pushover) { //add the key to the cache before sending, but with a short TTL - ctx.pushover.send(notify, function pushoverCallback(err, result) { + ctx.pushover.send(notify, function pushoverCallback (err, result) { if (err) { console.warn('Unable to send pushover', notify, err); } else { @@ -152,5 +155,4 @@ function init(env, ctx) { return pushnotify(); } - -module.exports = init; \ No newline at end of file +module.exports = init; diff --git a/lib/server/query.js b/lib/server/query.js index 0b5de5601b9..8279d5ad1e8 100644 --- a/lib/server/query.js +++ b/lib/server/query.js @@ -1,9 +1,10 @@ 'use strict'; -var traverse = require('traverse'); -var ObjectID = require('mongodb').ObjectID; +const traverse = require('traverse'); +const ObjectID = require('mongodb').ObjectID; +const moment = require('moment'); -var TWO_DAYS = 172800000; +const TWO_DAYS = 172800000; /** * @module query utilities * Assist in translating objects from query-string representation into @@ -56,6 +57,26 @@ function default_options (opts) { function enforceDateFilter (query, opts) { var dateValue = query[opts.dateField]; + // rewrite dates to ISO UTC strings so queries work as expected + if (dateValue) { + Object.keys(dateValue).forEach(function(key) { + let dateString = dateValue[key]; + if (isNaN(dateString)) { + dateString = dateString.replace(' ', '+'); // some clients don't excape the plus + + const validDate = moment(dateString).isValid(); + + if (!validDate) { + console.error('API request using an invalid date:', dateString); + throw new Error('Cannot parse ' + dateString + ' as a valid ISO-8601 date'); + } + + const d = moment.parseZone(dateString); + dateValue[key] = d.toISOString(); + } + }); + } + if (!dateValue && !query.dateString && true !== opts.noDateFilter) { var minDate = Date.now( ) - opts.deltaAgo; query[opts.dateField] = { @@ -94,7 +115,11 @@ function create (params, opts) { var query = finder && finder.find ? finder.find : { }; // Ensure some kind of sane date constraint tied to an index is expressed in the query. - enforceDateFilter(query, opts); + // unless an ID is provided, in which case assume the user knows what they are doing. + if (! query._id ) { + enforceDateFilter(query, opts); + } + // Help queries for _id. updateIdQuery(query); diff --git a/lib/server/treatments.js b/lib/server/treatments.js index 0ad9f4e8451..c02bd50f317 100644 --- a/lib/server/treatments.js +++ b/lib/server/treatments.js @@ -2,6 +2,7 @@ var _ = require('lodash'); var async = require('async'); +var moment = require('moment'); var find_options = require('./query'); @@ -24,6 +25,7 @@ function storage (env, ctx) { errs.push(err); callback(err, docs) }); + // eslint-disable-next-line no-unused-vars }, function (err, docs) { errs = _.compact(errs); done(errs.length > 0 ? errs : null, allDocs); @@ -96,11 +98,12 @@ function storage (env, ctx) { return find_options(opts, storage.queryOpts); } - - function remove (_id, fn) { - api( ).remove({ '_id': new ObjectID(_id) }, fn); - - ctx.bus.emit('data-received'); + function remove (opts, fn) { + return api( ).remove(query_for(opts), function (err, stat) { + //TODO: this is triggering a read from Mongo, we can do better + ctx.bus.emit('data-received'); + fn(err, stat); + }); } function save (obj, fn) { @@ -142,12 +145,20 @@ function storage (env, ctx) { function prepareData(obj) { + // Convert all dates to UTC dates + + const d = moment(obj.created_at).isValid() ? moment.parseZone(obj.created_at) : moment(); + obj.created_at = d.toISOString(); + var results = { - //TODO: validate format of created_at - created_at: obj.created_at || new Date().toISOString() + created_at: obj.created_at , preBolusCarbs: '' }; + const offset = d.utcOffset(); + obj.utcOffset = offset; + results.offset = offset; + obj.glucose = Number(obj.glucose); obj.targetTop = Number(obj.targetTop); obj.targetBottom = Number(obj.targetBottom); diff --git a/lib/server/websocket.js b/lib/server/websocket.js index 8143577c48e..afced8bfb37 100644 --- a/lib/server/websocket.js +++ b/lib/server/websocket.js @@ -34,6 +34,7 @@ function init (env, ctx, server) { }; // This is little ugly copy but I was unable to pass testa after making module from status and share with /api/v1/status + // eslint-disable-next-line no-unused-vars function status (profile) { var versionNum = 0; var verParse = /(\d+)\.(\d+)\.(\d+)*/.exec(env.version); @@ -82,6 +83,7 @@ function init (env, ctx, server) { read: false , write: false , write_treatment: false + , error: true }); } @@ -226,8 +228,9 @@ function init (env, ctx, server) { return; } + var objId = new ObjectID(data._id); ctx.store.collection(collection).update( - { '_id': new ObjectID(data._id) }, + { '_id': objId }, { $unset: data.data } ); @@ -325,8 +328,9 @@ function init (env, ctx, server) { if (err || array.length > 0) { console.log(LOG_DEDUP + 'Found similiar', array[0]); array[0].created_at = data.data.created_at; + var objId = new ObjectID(array[0]._id); ctx.store.collection(collection).update( - { '_id': new ObjectID(array[0]._id) }, + { '_id': objId }, { $set: {created_at: data.data.created_at} } ); if (callback) { @@ -338,6 +342,10 @@ function init (env, ctx, server) { // if not found create new record console.log(LOG_DEDUP + 'Adding new record'); ctx.store.collection(collection).insert(data.data, function insertResult (err, doc) { + if (err != null && err.message) { + console.log('treatments data insertion error: ', err.message); + return; + } if (callback) { callback(doc.ops); } @@ -367,6 +375,10 @@ function init (env, ctx, server) { } }); ctx.store.collection(collection).insert(data.data, function insertResult (err, doc) { + if (err != null && err.message) { + console.log('devicestatus insertion error: ', err.message); + return; + } if (callback) { callback(doc.ops); } @@ -374,6 +386,10 @@ function init (env, ctx, server) { }); } else { ctx.store.collection(collection).insert(data.data, function insertResult (err, doc) { + if (err != null && err.message) { + console.log(data.collection + ' insertion error: ', err.message); + return; + } if (callback) { callback(doc.ops); } @@ -398,8 +414,9 @@ function init (env, ctx, server) { return; } + var objId = new ObjectID(data._id); ctx.store.collection(collection).remove( - { '_id': new ObjectID(data._id) } + { '_id': objId } ); if (callback) { @@ -424,27 +441,20 @@ function init (env, ctx, server) { if (socketAuthorization.read) { socket.join('DataReceivers'); - var filterTreatments = false; var msecHistory = times.hours(history).msecs; // if `from` is received, it's a reconnection and full data is not needed if (from && from > 0) { - filterTreatments = true; msecHistory = Math.min(new Date().getTime() - from, msecHistory); } - // send all data upon new connection - if (lastData && lastData.splitRecent) { - var split = lastData.splitRecent(Date.now(), times.hours(3).msecs, msecHistory, filterTreatments); + + if (lastData && lastData.dataWithRecentStatuses) { + let data = lastData.dataWithRecentStatuses(); + if (message.status) { - split.first.status = status(split.first.profiles); + data.status = status(data.profiles); } - //send out first chunk - socket.emit('dataUpdate', split.first); - - //then send out the rest - setTimeout(function sendTheRest() { - split.rest.delta = true; - socket.emit('dataUpdate', split.rest); - }, 500); + + socket.emit('dataUpdate', data); } } console.log(LOG_WS + 'Authetication ID: ', socket.client.id, ' client: ', clientType, ' history: ' + history); @@ -504,6 +514,10 @@ function init (env, ctx, server) { start( ); listeners( ); + if (ctx.storageSocket) { + ctx.storageSocket.init(io); + } + return websocket(); } diff --git a/lib/settings.js b/lib/settings.js index ae5ce30e484..c2497ed66d5 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -2,11 +2,12 @@ var _ = require('lodash'); var levels = require('./levels'); +var constants = require('./constants.json'); -function init ( ) { +function init () { var settings = { - units: 'mg/dL' + units: 'mg/dl' , timeFormat: 12 , nightMode: false , editMode: true @@ -42,6 +43,14 @@ function init ( ) { , bgTargetBottom: 80 , bgLow: 55 } + , insecureUseHttp: false + , secureHstsHeader: true + , secureHstsHeaderIncludeSubdomains: false + , secureHstsHeaderPreload: false + , secureCsp: false + , deNormalizeDates: false + , showClockDelta: false + , showClockLastTime: false }; var valueMappers = { @@ -59,6 +68,16 @@ function init ( ) { , alarmTimeagoUrgent: mapTruthy , alarmWarnMins: mapNumberArray , timeFormat: mapNumber + , insecureUseHttp: mapTruthy + , secureHstsHeader: mapTruthy + , secureCsp: mapTruthy + , deNormalizeDates: mapTruthy + , showClockDelta: mapTruthy + , showClockLastTime: mapTruthy + , bgHigh: mapNumber + , bgLow: mapNumber + , bgTargetTop: mapNumber + , bgTargetBottom: mapNumber }; function mapNumberArray (value) { @@ -68,7 +87,7 @@ function init ( ) { if (isNaN(value)) { var rawValues = value && value.split(' ') || []; - return _.map(rawValues, function (num) { + return _.map(rawValues, function(num) { return isNaN(num) ? null : Number(num); }); } else { @@ -81,6 +100,11 @@ function init ( ) { return value; } + if (typeof value === 'string' && isNaN(value)) { + const decommaed = value.replace(',','.'); + if (!isNaN(decommaed)) { value = decommaed; } + } + if (isNaN(value)) { return value; } else { @@ -144,18 +168,18 @@ function init ( ) { } function anyEnabled (features) { - return _.findIndex(features, function (feature) { + return _.findIndex(features, function(feature) { return enable.indexOf(feature) > -1; }) > -1; } - function prepareAlarmTypes ( ) { + function prepareAlarmTypes () { var alarmTypes = _.filter(getAndPrepare('alarmTypes'), function onlyKnownTypes (type) { return type === 'predict' || type === 'simple'; }); if (alarmTypes.length === 0) { - var thresholdWasSet = _.findIndex(wasSet, function (name) { + var thresholdWasSet = _.findIndex(wasSet, function(name) { return name.indexOf('bg') === 0; }) > -1; alarmTypes = thresholdWasSet ? ['simple'] : ['predict']; @@ -196,11 +220,19 @@ function init ( ) { thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom); thresholds.bgLow = Number(thresholds.bgLow); + // Do not convert for old installs that have these set in mg/dl + if (settings.units.toLowerCase().includes('mmol') && thresholds.bgHigh < 50) { + thresholds.bgHigh = Math.round(thresholds.bgHigh * constants.MMOL_TO_MGDL); + thresholds.bgTargetTop = Math.round(thresholds.bgTargetTop * constants.MMOL_TO_MGDL); + thresholds.bgTargetBottom = Math.round(thresholds.bgTargetBottom * constants.MMOL_TO_MGDL); + thresholds.bgLow = Math.round(thresholds.bgLow * constants.MMOL_TO_MGDL); + } + verifyThresholds(); adjustShownPlugins(); } - function verifyThresholds() { + function verifyThresholds () { var thresholds = settings.thresholds; if (thresholds.bgTargetBottom >= thresholds.bgTargetTop) { @@ -225,7 +257,7 @@ function init ( ) { } } - function adjustShownPlugins ( ) { + function adjustShownPlugins () { var showPluginsUnset = settings.showPlugins && 0 === settings.showPlugins.length; settings.showPlugins += ' delta direction upbat'; @@ -236,7 +268,7 @@ function init ( ) { if (showPluginsUnset) { //assume all enabled features are plugins and they should be shown for now //it would be better to use the registered plugins, but it's not loaded yet... - _.forEach(settings.enable, function showFeature(feature) { + _.forEach(settings.enable, function showFeature (feature) { if (isEnabled(feature)) { settings.showPlugins += ' ' + feature; } @@ -280,7 +312,7 @@ function init ( ) { var snoozeTime; if (notify.eventName === 'high' && notify.level === levels.URGENT && settings.alarmUrgentHigh) { - snoozeTime = settings.alarmUrgentHighMins; + snoozeTime = settings.alarmUrgentHighMins; } else if (notify.eventName === 'high' && settings.alarmHigh) { snoozeTime = settings.alarmHighMins; } else if (notify.eventName === 'low' && notify.level === levels.URGENT && settings.alarmUrgentLow) { diff --git a/lib/storage/mongo-storage.js b/lib/storage/mongo-storage.js index b132e42dc4c..275cde6eb8a 100644 --- a/lib/storage/mongo-storage.js +++ b/lib/storage/mongo-storage.js @@ -25,7 +25,8 @@ function init (env, cb, forceNewConnection) { console.log('Setting up new connection to MongoDB'); var timeout = 30 * 1000; - var options = { reconnectInterval: 10000, reconnectTries: 500, connectTimeoutMS: timeout, socketTimeoutMS: timeout }; + var options = { reconnectInterval: 10000, reconnectTries: 500, connectTimeoutMS: timeout, + socketTimeoutMS: timeout, useNewUrlParser: true }; var connect_with_retry = function(i) { return MongoClient.connect(env.storageURI, options, function connected(err, client) { diff --git a/lib/units.js b/lib/units.js index f548d55744e..5eb36c10950 100644 --- a/lib/units.js +++ b/lib/units.js @@ -1,11 +1,13 @@ 'use strict'; +var consts = require('./constants'); + function mgdlToMMOL(mgdl) { - return (Math.round((mgdl / 18) * 10) / 10).toFixed(1); + return (Math.round((mgdl / consts.MMOL_TO_MGDL) * 10) / 10).toFixed(1); } function mmolToMgdl(mgdl) { - return Math.round(mgdl * 18); + return Math.round(mgdl * consts.MMOL_TO_MGDL); } function configure() { diff --git a/lib/utils.js b/lib/utils.js index 1d407ccda05..083c4284846 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -26,7 +26,7 @@ function init(ctx) { }; utils.toFixed = function toFixed(value) { - if (value === undefined || value === 0) { + if (!value) { return '0'; } else { var fixed = value.toFixed(2); @@ -34,6 +34,16 @@ function init(ctx) { } }; + utils.toFixedMin = function toFixedMin(value,digits) { + if (!value) { + return '0'; + } + var mult = Math.pow(10,digits); + var fixed = Math.sign(value) * Math.round(Math.abs(value)*mult) / mult; + if (isNaN(fixed)) return '0'; + return String(fixed); + }; + // some helpers for input "date" utils.mergeInputTime = function mergeInputTime(timestring, datestring) { return moment(datestring + ' ' + timestring, 'YYYY-MM-D HH:mm'); @@ -70,4 +80,4 @@ function init(ctx) { return utils; } -module.exports = init; \ No newline at end of file +module.exports = init; diff --git a/my.env.template b/my.env.template new file mode 100644 index 00000000000..0ca76ea5209 --- /dev/null +++ b/my.env.template @@ -0,0 +1,14 @@ +CUSTOMCONNSTR_mongo=mongodb://.... +CUSTOMCONNSTR_mongo_collection= +MONGO_PROFILE_COLLECTION= +API_SECRET=1234567890abc +HOSTNAME=0.0.0.0 +ENABLE="devicestatus rawbg upbat careportal iob profile cage bage avg cob basal treatments sage boluscalc pump openaps iage speech" +SHOW_PLUGINS="rawbg-on careportal upbat iob profile cage cob basal avg treatments boluscalc pump openaps iage speech" +DISPLAY_UNITS="mmol" +TIME_FORMAT=24 +ALARM_TYPES="predict" +LANGUAGE=en +INSECURE_USE_HTTP=true +PORT=1337 +NODE_ENV=development \ No newline at end of file diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f8d6d9b757c..7d3a1730a7d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,394 +1,1534 @@ { - "name": "Nightscout", - "version": "0.10.3-master-20180805", + "name": "nightscout", + "version": "13.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { - "@webassemblyjs/ast": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", - "integrity": "sha512-49nwvW/Hx9i+OYHg+mRhKZfAlqThr11Dqz8TsrvqGKMhdI2ijy3KBJOun2Z4770TPjrIJhR6KxChQIDaz8clDA==", - "dev": true, - "requires": { - "@webassemblyjs/helper-module-context": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/wast-parser": "1.5.13", - "debug": "^3.1.0", - "mamacro": "^0.0.3" + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.2.tgz", + "integrity": "sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==", + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.2", + "@babel/helpers": "^7.6.2", + "@babel/parser": "^7.6.2", + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.2", + "@babel/types": "^7.6.0", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", + "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", + "requires": { + "@babel/types": "^7.6.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/parser": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==" + }, + "@babel/template": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" + } + }, + "@babel/traverse": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", + "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.2", + "@babel/types": "^7.6.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json5": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "requires": { + "minimist": "^1.2.0" } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz", - "integrity": "sha512-vrvvB18Kh4uyghSKb0NTv+2WZx871WL2NzwMj61jcq2bXkyhRC+8Q0oD7JGVf0+5i/fKQYQSBCNMMsDMRVAMqA==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz", - "integrity": "sha512-dBh2CWYqjaDlvMmRP/kudxpdh30uXjIbpkLj9HQe+qtYlwvYjPRjdQXrq1cTAAOUSMTtzqbXIxEdEZmyKfcwsg==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz", - "integrity": "sha512-v7igWf1mHcpJNbn4m7e77XOAWXCDT76Xe7Is1VQFXc4K5jRcFrl9D0NrqM4XifQ0bXiuTSkTKMYqDxu5MhNljA==", - "dev": true, - "requires": { - "debug": "^3.1.0" + "@babel/generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", + "requires": { + "@babel/types": "^7.4.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, - "@webassemblyjs/helper-code-frame": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz", - "integrity": "sha512-yN6ScQQDFCiAXnVctdVO/J5NQRbwyTbQzsGzEgXsAnrxhjp0xihh+nNHQTMrq5UhOqTb5LykpJAvEv9AT0jnAQ==", - "dev": true, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", "requires": { - "@webassemblyjs/wast-printer": "1.5.13" + "@babel/types": "^7.0.0" } }, - "@webassemblyjs/helper-fsm": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz", - "integrity": "sha512-hSIKzbXjVMRvy3Jzhgu+vDd/aswJ+UMEnLRCkZDdknZO3Z9e6rp1DAs0tdLItjCFqkz9+0BeOPK/mk3eYvVzZg==", - "dev": true + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "@webassemblyjs/helper-module-context": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz", - "integrity": "sha512-zxJXULGPLB7r+k+wIlvGlXpT4CYppRz8fLUM/xobGHc9Z3T6qlmJD9ySJ2jknuktuuiR9AjnNpKYDECyaiX+QQ==", - "dev": true, + "@babel/helper-call-delegate": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", + "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", "requires": { - "debug": "^3.1.0", - "mamacro": "^0.0.3" + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" }, "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", + "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", + "requires": { + "@babel/types": "^7.6.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/parser": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==" + }, + "@babel/traverse": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", + "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.2", + "@babel/types": "^7.6.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz", - "integrity": "sha512-0n3SoNGLvbJIZPhtMFq0XmmnA/YmQBXaZKQZcW8maGKwLpVcgjNrxpFZHEOLKjXJYVN5Il8vSfG7nRX50Zn+aw==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz", - "integrity": "sha512-IJ/goicOZ5TT1axZFSnlAtz4m8KEjYr12BNOANAwGFPKXM4byEDaMNXYowHMG0yKV9a397eU/NlibFaLwr1fbw==", - "dev": true, + "@babel/helper-define-map": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", + "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-buffer": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/wasm-gen": "1.5.13", - "debug": "^3.1.0" + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", "requires": { - "ms": "2.0.0" + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" } } } }, - "@webassemblyjs/ieee754": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz", - "integrity": "sha512-TseswvXEPpG5TCBKoLx9tT7+/GMACjC1ruo09j46ULRZWYm8XHpDWaosOjTnI7kr4SRJFzA6MWoUkAB+YCGKKg==", - "dev": true, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", "requires": { - "ieee754": "^1.1.11" + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "@webassemblyjs/leb128": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.5.13.tgz", - "integrity": "sha512-0NRMxrL+GG3eISGZBmLBLAVjphbN8Si15s7jzThaw1UE9e5BY1oH49/+MA1xBzxpf1OW5sf9OrPDOclk9wj2yg==", - "dev": true, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", + "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", "requires": { - "long": "4.0.0" + "@babel/types": "^7.4.4" }, "dependencies": { - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } } } }, - "@webassemblyjs/utf8": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.5.13.tgz", - "integrity": "sha512-Ve1ilU2N48Ew0lVGB8FqY7V7hXjaC4+PeZM+vDYxEd+R2iQ0q+Wb3Rw8v0Ri0+rxhoz6gVGsnQNb4FjRiEH/Ng==", - "dev": true + "@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "requires": { + "@babel/types": "^7.5.5" + }, + "dependencies": { + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } }, - "@webassemblyjs/wasm-edit": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz", - "integrity": "sha512-X7ZNW4+Hga4f2NmqENnHke2V/mGYK/xnybJSIXImt1ulxbCOEs/A+ZK/Km2jgihjyVxp/0z0hwIcxC6PrkWtgw==", - "dev": true, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", + "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-buffer": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/helper-wasm-section": "1.5.13", - "@webassemblyjs/wasm-gen": "1.5.13", - "@webassemblyjs/wasm-opt": "1.5.13", - "@webassemblyjs/wasm-parser": "1.5.13", - "@webassemblyjs/wast-printer": "1.5.13", - "debug": "^3.1.0" + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", "requires": { - "ms": "2.0.0" + "@babel/types": "^7.4.4" + } + }, + "@babel/parser": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==" + }, + "@babel/template": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" + } + }, + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" } } } }, - "@webassemblyjs/wasm-gen": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz", - "integrity": "sha512-yfv94Se8R73zmr8GAYzezFHc3lDwE/lBXQddSiIZEKZFuqy7yWtm3KMwA1uGbv5G1WphimJxboXHR80IgX1hQA==", - "dev": true, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/ieee754": "1.5.13", - "@webassemblyjs/leb128": "1.5.13", - "@webassemblyjs/utf8": "1.5.13" + "@babel/types": "^7.0.0" } }, - "@webassemblyjs/wasm-opt": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz", - "integrity": "sha512-IkXSkgzVhQ0QYAdIayuCWMmXSYx0dHGU8Ah/AxJf1gBvstMWVnzJnBwLsXLyD87VSBIcsqkmZ28dVb0mOC3oBg==", - "dev": true, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + }, + "@babel/helper-regex": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", + "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-buffer": "1.5.13", - "@webassemblyjs/wasm-gen": "1.5.13", - "@webassemblyjs/wasm-parser": "1.5.13", - "debug": "^3.1.0" + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" }, "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", + "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", + "requires": { + "@babel/types": "^7.6.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/parser": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==" + }, + "@babel/traverse": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", + "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.2", + "@babel/types": "^7.6.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, - "@webassemblyjs/wasm-parser": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz", - "integrity": "sha512-XnYoIcu2iqq8/LrtmdnN3T+bRjqYFjRHqWbqK3osD/0r/Fcv4d9ecRzjVtC29ENEuNTK4mQ9yyxCBCbK8S/cpg==", - "dev": true, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-api-error": "1.5.13", - "@webassemblyjs/helper-wasm-bytecode": "1.5.13", - "@webassemblyjs/ieee754": "1.5.13", - "@webassemblyjs/leb128": "1.5.13", - "@webassemblyjs/utf8": "1.5.13" + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "@webassemblyjs/wast-parser": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz", - "integrity": "sha512-Lbz65T0LQ1LgzKiUytl34CwuhMNhaCLgrh0JW4rJBN6INnBB8NMwUfQM+FxTnLY9qJ+lHJL/gCM5xYhB9oWi4A==", - "dev": true, + "@babel/helper-split-export-declaration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/floating-point-hex-parser": "1.5.13", - "@webassemblyjs/helper-api-error": "1.5.13", - "@webassemblyjs/helper-code-frame": "1.5.13", - "@webassemblyjs/helper-fsm": "1.5.13", - "long": "^3.2.0", - "mamacro": "^0.0.3" + "@babel/types": "^7.4.0" } }, - "@webassemblyjs/wast-printer": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz", - "integrity": "sha512-QcwogrdqcBh8Z+eUF8SG+ag5iwQSXxQJELBEHmLkk790wgQgnIMmntT2sMAMw53GiFNckArf5X0bsCA44j3lWQ==", - "dev": true, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/wast-parser": "1.5.13", - "long": "^3.2.0" + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" } }, - "@webpack-contrib/config-loader": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@webpack-contrib/config-loader/-/config-loader-1.2.1.tgz", - "integrity": "sha512-C7XsS6bXft0aRlyt7YCLg+fm97Mb3tWd+i5fVVlEl0NW5HKy8LoXVKj3mB7ECcEHNEEdHhgzg8gxP+Or8cMj8Q==", - "dev": true, + "@babel/helpers": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz", + "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==", "requires": { - "@webpack-contrib/schema-utils": "^1.0.0-beta.0", - "chalk": "^2.1.0", - "cosmiconfig": "^5.0.2", - "is-plain-obj": "^1.1.0", - "loud-rejection": "^1.6.0", - "merge-options": "^1.0.1", - "minimist": "^1.2.0", - "resolve": "^1.6.0", - "webpack-log": "^1.1.2" + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.2", + "@babel/types": "^7.6.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", + "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", + "requires": { + "@babel/types": "^7.6.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/parser": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", + "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==" + }, + "@babel/template": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" + } + }, + "@babel/traverse": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", + "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.2", + "@babel/types": "^7.6.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "requires": { + "@babel/highlight": "^7.0.0" + } + } + } + }, + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } } } }, - "@webpack-contrib/schema-utils": { - "version": "1.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@webpack-contrib/schema-utils/-/schema-utils-1.0.0-beta.0.tgz", - "integrity": "sha512-LonryJP+FxQQHsjGBi6W786TQB1Oym+agTpY0c+Kj8alnIw+DLUJb6SI8Y1GHGhLCH1yPRrucjObUmxNICQ1pg==", - "dev": true, + "@babel/parser": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz", + "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chalk": "^2.3.2", - "strip-ansi": "^4.0.0", - "text-table": "^0.2.0", - "webpack-log": "^1.1.2" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", + "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", + "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz", + "integrity": "sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==" + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", "requires": { - "color-convert": "^1.9.0" + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", + "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz", + "integrity": "sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", + "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5", + "@babel/helper-split-export-declaration": "^7.4.4", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "requires": { + "@babel/types": "^7.4.4" } }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz", + "integrity": "sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz", + "integrity": "sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + }, + "dependencies": { + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==" }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", "requires": { - "ansi-regex": "^3.0.0" + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", + "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", + "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", + "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", + "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz", + "integrity": "sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==", + "requires": { + "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", + "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", + "requires": { + "@babel/helper-hoist-variables": "^7.4.4", + "@babel/helper-plugin-utils": "^7.0.0", + "babel-plugin-dynamic-import-node": "^2.3.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz", + "integrity": "sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g==", + "requires": { + "regexpu-core": "^4.6.0" + }, + "dependencies": { + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" } }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==" + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", "requires": { - "has-flag": "^3.0.0" + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", + "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", + "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", + "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "requires": { + "@babel/helper-call-delegate": "^7.4.4", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", + "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "requires": { + "regenerator-transform": "^0.14.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", + "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz", + "integrity": "sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", + "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz", + "integrity": "sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" + }, + "dependencies": { + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==" + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "requires": { + "jsesc": "~0.5.0" + } + } + } + }, + "@babel/preset-env": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.2.tgz", + "integrity": "sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-dynamic-import": "^7.5.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.6.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.6.2", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.5.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.6.2", + "@babel/plugin-transform-classes": "^7.5.5", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.6.0", + "@babel/plugin-transform-dotall-regex": "^7.6.2", + "@babel/plugin-transform-duplicate-keys": "^7.5.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.4", + "@babel/plugin-transform-function-name": "^7.4.4", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-member-expression-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.5.0", + "@babel/plugin-transform-modules-commonjs": "^7.6.0", + "@babel/plugin-transform-modules-systemjs": "^7.5.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.2", + "@babel/plugin-transform-new-target": "^7.4.4", + "@babel/plugin-transform-object-super": "^7.5.5", + "@babel/plugin-transform-parameters": "^7.4.4", + "@babel/plugin-transform-property-literals": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.4.5", + "@babel/plugin-transform-reserved-words": "^7.2.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.6.2", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.4.4", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.6.2", + "@babel/types": "^7.6.0", + "browserslist": "^4.6.0", + "core-js-compat": "^3.1.1", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.5.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", + "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/traverse": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz", + "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/types": "^7.4.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@webassemblyjs/ast": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "requires": { + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==" + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", + "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "requires": { + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==" + }, + "@webassemblyjs/helper-module-context": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", + "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", + "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", + "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", + "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", + "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/helper-wasm-section": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-opt": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", + "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", + "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", + "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/ieee754": "1.8.5", + "@webassemblyjs/leb128": "1.8.5", + "@webassemblyjs/utf8": "1.8.5" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", + "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/floating-point-hex-parser": "1.8.5", + "@webassemblyjs/helper-api-error": "1.8.5", + "@webassemblyjs/helper-code-frame": "1.8.5", + "@webassemblyjs/helper-fsm": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", + "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=" + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -396,79 +1536,78 @@ "dev": true }, "accepts": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", - "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "requires": { - "mime-types": "~2.1.11", + "mime-types": "~2.1.18", "negotiator": "0.6.1" } }, "acorn": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", - "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", - "dev": true + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" }, - "acorn-dynamic-import": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", - "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", - "dev": true, + "acorn-globals": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", + "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", "requires": { - "acorn": "^5.0.0" + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==" + } } }, + "acorn-jsx": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", + "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", + "dev": true + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==" + }, "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, "ajv": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", - "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" + "uri-js": "^4.2.2" } }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "dependencies": { - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - } - } + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + "ajv-keywords": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==" }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true + "dev": true, + "optional": true }, "ansi-align": { "version": "2.0.0", @@ -477,8 +1616,62 @@ "dev": true, "requires": { "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz", + "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==", + "dev": true, + "requires": { + "type-fest": "^0.5.2" } }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -493,22 +1686,73 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "apn": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/apn/-/apn-2.2.0.tgz", + "integrity": "sha512-YIypYzPVJA9wzNBLKZ/mq2l1IZX/2FadPvwmSv4ZeR0VH7xdNITQ6Pucgh0Uw6ZZKC+XwheaJ57DFZAhJ0FvPg==", + "requires": { + "debug": "^3.1.0", + "http2": "https://github.com/node-apn/node-http2/archive/apn-2.1.4.tar.gz", + "jsonwebtoken": "^8.1.0", + "node-forge": "^0.7.1", + "verror": "^1.10.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" } }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -516,73 +1760,50 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=" - }, "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "ascli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", - "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=", + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { - "colour": "~0.7.1", - "optjs": "~3.2.2" + "safer-buffer": "~2.1.0" } }, "asn1.js": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -590,35 +1811,43 @@ } }, "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "dev": true, + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "requires": { + "object-assign": "^4.1.1", "util": "0.10.3" }, "dependencies": { "inherits": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, "requires": { "inherits": "2.0.1" } } } }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, "async": { @@ -627,10 +1856,9 @@ "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" }, "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" }, "async-limiter": { "version": "1.0.0", @@ -643,28 +1871,19 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", - "dev": true + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", - "requires": { - "browserslist": "^1.7.6", - "caniuse-db": "^1.0.30000634", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^5.2.16", - "postcss-value-parser": "^3.2.3" - } + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "babel-code-frame": { "version": "6.26.0", @@ -676,6 +1895,86 @@ "js-tokens": "^3.0.2" } }, + "babel-eslint": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", + "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, + "babel-loader": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", + "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "requires": { + "find-cache-dir": "^2.0.0", + "loader-utils": "^1.0.2", + "mkdirp": "^0.5.1", + "pify": "^4.0.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "requires": { + "object.assign": "^4.1.0" + } + }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -690,7 +1989,6 @@ "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -705,7 +2003,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -714,7 +2011,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -723,7 +2019,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -732,18 +2027,11 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -753,157 +2041,109 @@ "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", - "dev": true + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "base64id": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, "requires": { "tweetnacl": "^0.14.3" } }, "benv": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/benv/-/benv-3.3.0.tgz", - "integrity": "sha1-c3XsAalaAAM+uYCESINmKFKCJDU=", - "dev": true, - "requires": { - "jsdom": ">= 10.0", - "rewire": "^2.3.1" - } - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "requires": { - "callsite": "1.0.0" - } - }, - "bfj-node4": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/bfj-node4/-/bfj-node4-5.3.1.tgz", - "integrity": "sha512-SOmOsowQWfXc7ybFARsK3C4MCOWzERaOMV/Fl3Tgjs+5dJWyzo3oa127jL44eMbQiAN17J7SvAs2TRxEScTUmg==", - "dev": true, - "requires": { - "bluebird": "^3.5.1", - "check-types": "^7.3.0", - "tryer": "^1.0.0" - }, - "dependencies": { - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true - } - } - }, - "big.js": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz", - "integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=" - }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "dev": true - }, - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "resolved": "https://registry.npmjs.org/benv/-/benv-3.3.0.tgz", + "integrity": "sha1-c3XsAalaAAM+uYCESINmKFKCJDU=", + "dev": true, + "requires": { + "jsdom": ">= 10.0", + "rewire": "^2.3.1" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "bfj": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", + "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "check-types": "^8.0.3", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" } }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, "blob": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", - "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" }, "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "requires": { - "bytes": "3.0.0", + "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" }, "dependencies": { - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" } } }, @@ -915,6 +2155,11 @@ "chainsaw": "~0.1.0" } }, + "bowser": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.7.0.tgz", + "integrity": "sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w==" + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -930,6 +2175,12 @@ "widest-line": "^2.0.0" }, "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -939,16 +2190,10 @@ "color-convert": "^1.9.0" } }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -956,16 +2201,35 @@ "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -974,33 +2238,31 @@ } }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "braces": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", - "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "requires": { - "expand-range": "^0.1.0" + "fill-range": "^7.0.1" } }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, "browser-process-hrtime": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", - "integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=", - "dev": true + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" }, "browser-stdout": { "version": "1.3.1", @@ -1012,7 +2274,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -1026,7 +2287,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, "requires": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", @@ -1037,27 +2297,17 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } } }, "browserify-rsa": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" @@ -1067,7 +2317,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, "requires": { "bn.js": "^4.1.1", "browserify-rsa": "^4.0.0", @@ -1082,34 +2331,40 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, "requires": { "pako": "~1.0.5" } }, "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.0.tgz", + "integrity": "sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==", "requires": { - "caniuse-db": "^1.0.30000639", - "electron-to-chromium": "^1.2.7" + "caniuse-lite": "^1.0.30000989", + "electron-to-chromium": "^1.3.247", + "node-releases": "^1.1.29" } }, "bson": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.4.tgz", - "integrity": "sha1-k8ENOeqltYQVy8QFLz5T5WKwtyw=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz", + "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==" }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + } } }, "buffer-equal-constant-time": { @@ -1118,48 +2373,19 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "bufferview": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bufferview/-/bufferview-1.0.1.tgz", - "integrity": "sha1-ev10pF+Tf6QiodM4wIu/3HbNcl0=" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "bytebuffer": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-3.1.1.tgz", - "integrity": "sha1-KGuLPxZz43kPX7K+Iu+l61uW25A=", - "requires": { - "bufferview": "~1", - "long": "~1" - }, - "dependencies": { - "long": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-1.2.3.tgz", - "integrity": "sha1-EX/i4rHEU9g+Mid2yNRwY5+qK38=" - } - } + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" }, "bytes": { "version": "3.0.0", @@ -1167,31 +2393,59 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "cacache": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", - "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", - "dev": true, - "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.1", - "mississippi": "^2.0.0", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", - "ssri": "^5.2.4", - "unique-filename": "^1.1.0", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", "y18n": "^4.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } } }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -1204,31 +2458,40 @@ "unset-value": "^1.0.0" } }, - "callback-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz", - "integrity": "sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg=", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "> 1.0.0 < 3.0.0" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, - "requires": { - "callsites": "^2.0.0" - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", "dev": true, "requires": { - "caller-callsite": "^2.0.0" + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "callsite": { @@ -1237,69 +2500,37 @@ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" }, "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true - }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true }, - "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", - "requires": { - "browserslist": "^1.3.6", - "caniuse-db": "^1.0.30000529", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, - "caniuse-db": { - "version": "1.0.30000862", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000862.tgz", - "integrity": "sha1-bB4pb4u+Xl6kbwQhXouQ7Y+52o0=" + "caniuse-lite": { + "version": "1.0.30000999", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz", + "integrity": "sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg==" }, "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", "dev": true }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chainsaw": { "version": "0.1.0", @@ -1328,44 +2559,46 @@ "supports-color": "^2.0.0" } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "check-types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz", - "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", + "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", "dev": true }, "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", - "dev": true, + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "requires": { "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", - "inherits": "^2.0.1", + "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", + "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "readdirp": "^2.2.1", + "upath": "^1.1.1" }, "dependencies": { - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -1383,57 +2616,99 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", - "dev": true + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" }, "chrome-trace-event": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz", - "integrity": "sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "requires": { "tslib": "^1.9.0" } }, "ci-info": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", - "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", "dev": true }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "requires": { - "chalk": "^1.1.3" - } - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -1445,7 +2720,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -1453,21 +2727,11 @@ } }, "clean-css": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", - "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", - "requires": { - "source-map": "0.5.x" - } - }, - "clear-require": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clear-require/-/clear-require-2.0.0.tgz", - "integrity": "sha1-qgH1w1WJMmvVXphp6r2kj4ZEfes=", - "dev": true, + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^2.0.0" + "source-map": "~0.6.0" } }, "cli-boxes": { @@ -1477,149 +2741,109 @@ "dev": true }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, - "cli-spinners": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz", - "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } } } }, "clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", - "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=" - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "requires": { - "q": "^1.1.2" - } + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" } }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "requires": { - "clone": "^1.0.2", - "color-convert": "^1.3.0", - "color-string": "^0.3.0" - } - }, "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "color-name": "1.1.1" + "color-name": "1.1.3" } }, "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" - }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "requires": { - "color-name": "^1.0.0" - } - }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "requires": { - "color": "^0.11.0", - "css-color-names": "0.0.4", - "has": "^1.0.1" - } - }, - "colour": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", - "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", - "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==" - }, - "commist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/commist/-/commist-1.0.0.tgz", - "integrity": "sha1-wMNSUBz29S6RJOPvicmAbiAi6+8=", - "requires": { - "leven": "^1.0.0", - "minimist": "^1.1.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, "component-bind": { "version": "1.0.0", @@ -1637,61 +2861,25 @@ "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, "compressible": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz", - "integrity": "sha1-MmxfUH+7BV9UEWeCuWmoG2einac=", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.16.tgz", + "integrity": "sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA==", "requires": { - "mime-db": ">= 1.34.0 < 2" - }, - "dependencies": { - "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" - } + "mime-db": ">= 1.38.0 < 2" } }, "compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "requires": { "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.14", + "compressible": "~2.0.16", "debug": "2.6.9", - "on-headers": "~1.0.1", + "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" - }, - "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" - }, - "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", - "requires": { - "mime-db": "~1.35.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } } }, "concat-map": { @@ -1728,7 +2916,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, "requires": { "date-now": "^0.1.4" } @@ -1736,19 +2923,34 @@ "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" }, "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-security-policy-builder": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz", + "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==" }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -1769,7 +2971,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, "requires": { "aproba": "^1.1.1", "fs-write-stream-atomic": "^1.0.8", @@ -1782,30 +2983,63 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-js-compat": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.2.1.tgz", + "integrity": "sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A==", + "requires": { + "browserslist": "^4.6.6", + "semver": "^6.3.0" + } }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cosmiconfig": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.5.tgz", - "integrity": "sha512-94j37OtvxS5w7qr7Ta6dt67tWdnOxigBVN4VnSxNXFez9o18PGQ0D33SchKP17r9LAcWVTYV72G6vDayAUBFIg==", + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", "dev": true, "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^4.0.0" + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", - "dev": true, "requires": { "bn.js": "^4.1.0", "elliptic": "^6.0.0" @@ -1824,7 +3058,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -1837,7 +3070,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -1848,21 +3080,33 @@ } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + } } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, "requires": { "browserify-cipher": "^1.0.0", "browserify-sign": "^4.0.0", @@ -1883,43 +3127,29 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" - }, "css-loader": { - "version": "0.28.11", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", - "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.1.tgz", + "integrity": "sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==", "requires": { "babel-code-frame": "^6.26.0", "css-selector-tokenizer": "^0.7.0", - "cssnano": "^3.10.0", "icss-utils": "^2.1.0", "loader-utils": "^1.0.2", - "lodash.camelcase": "^4.3.0", - "object-assign": "^4.1.1", - "postcss": "^5.0.6", + "lodash": "^4.17.11", + "postcss": "^6.0.23", "postcss-modules-extract-imports": "^1.2.0", "postcss-modules-local-by-default": "^1.2.0", "postcss-modules-scope": "^1.1.0", "postcss-modules-values": "^1.3.0", "postcss-value-parser": "^3.3.0", "source-list-map": "^2.0.0" - }, - "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - } } }, "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", "requires": { "cssesc": "^0.1.0", "fastparse": "^1.1.1", @@ -1936,159 +3166,320 @@ "resolved": "https://registry.npmjs.org/cssmin/-/cssmin-0.4.3.tgz", "integrity": "sha1-yRlAd+Dr2s1pHV9ZAVudgZ840BU=" }, - "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==" + }, + "cssstyle": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.3.1.tgz", + "integrity": "sha512-tNvaxM5blOnxanyxI6panOsnfiyLRj3HV4qjqqS45WPNS1usdYWRUQjqTEEELK73lpeP/1KoIGYUwrBn/VcECA==", "requires": { - "autoprefixer": "^6.3.1", - "decamelize": "^1.1.2", - "defined": "^1.0.0", - "has": "^1.0.1", - "object-assign": "^4.0.1", - "postcss": "^5.0.14", - "postcss-calc": "^5.2.0", - "postcss-colormin": "^2.1.8", - "postcss-convert-values": "^2.3.4", - "postcss-discard-comments": "^2.0.4", - "postcss-discard-duplicates": "^2.0.1", - "postcss-discard-empty": "^2.0.1", - "postcss-discard-overridden": "^0.1.1", - "postcss-discard-unused": "^2.2.1", - "postcss-filter-plugins": "^2.0.0", - "postcss-merge-idents": "^2.1.5", - "postcss-merge-longhand": "^2.0.1", - "postcss-merge-rules": "^2.0.3", - "postcss-minify-font-values": "^1.0.2", - "postcss-minify-gradients": "^1.0.1", - "postcss-minify-params": "^1.0.4", - "postcss-minify-selectors": "^2.0.4", - "postcss-normalize-charset": "^1.1.0", - "postcss-normalize-url": "^3.0.7", - "postcss-ordered-values": "^2.1.0", - "postcss-reduce-idents": "^2.2.2", - "postcss-reduce-initial": "^1.0.0", - "postcss-reduce-transforms": "^1.0.3", - "postcss-svgo": "^2.1.1", - "postcss-unique-selectors": "^2.0.2", - "postcss-value-parser": "^3.2.3", - "postcss-zindex": "^2.0.1" - }, - "dependencies": { - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - } + "cssom": "0.3.x" } }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" + }, + "d3": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.12.0.tgz", + "integrity": "sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==", + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "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==", "requires": { - "clap": "^1.0.9", - "source-map": "^0.5.3" + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" } }, - "cssom": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", - "dev": true + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "requires": { + "d3-array": "1", + "d3-path": "1" + } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "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==" + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", "requires": { - "array-find-index": "^1.0.1" + "d3-array": "^1.1.1" } }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", - "dev": true + "d3-dispatch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", + "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, + "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==", + "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==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "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==" + }, + "d3-fetch": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", + "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "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==" + }, + "d3-geo": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz", + "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==", + "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==" + }, + "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==", + "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==" + }, + "d3-polygon": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", + "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" + }, + "d3-quadtree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", + "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==" + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "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==" + }, + "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==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "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==", + "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==" + }, + "d3-transition": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", + "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", "requires": { - "es5-ext": "^0.10.9" + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" } }, - "d3": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", - "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=" - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, + "dasherize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", + "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" + }, "data-urls": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.0.tgz", - "integrity": "sha512-ai40PPQR0Fn1lD2PPie79CibnlMN2AYiDhwFX/rZHVsxbs5kNJSjegqXIprhouGXlRdEnfybva7kqRGnB6mypA==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", "requires": { - "abab": "^1.0.4", - "whatwg-mimetype": "^2.0.0", - "whatwg-url": "^6.4.0" + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" }, "dependencies": { "abab": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", - "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" }, "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", "requires": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -2100,8 +3491,7 @@ "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" }, "debug": { "version": "2.6.9", @@ -2116,29 +3506,10 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - } - } - }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "deep-extend": { "version": "0.6.0", @@ -2149,33 +3520,29 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", "dev": true, "requires": { - "clone": "^1.0.2" + "strip-bom": "^3.0.0" } }, "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" + "object-keys": "^1.0.12" } }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -2185,7 +3552,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -2194,7 +3560,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -2203,18 +3568,11 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -2232,7 +3590,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true, "requires": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" @@ -2243,6 +3600,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -2253,36 +3615,44 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, "requires": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, + "dns-prefetch-control": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz", + "integrity": "sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q==" + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" }, "domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, "requires": { "webidl-conversions": "^4.0.2" - }, - "dependencies": { - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - } } }, + "dont-sniff-mimetype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz", + "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==" + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -2304,9 +3674,9 @@ "dev": true }, "duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -2315,18 +3685,18 @@ } }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "requires": { "safe-buffer": "^5.0.1" } @@ -2337,20 +3707,19 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", - "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.1.tgz", + "integrity": "sha512-kS/gEPzZs3Y1rRsbGX4UOSjtP/CeJP0CxSNZHYxGfVM/VgLcv0ZqM7C45YyTj2DI2g7+P9Dd24C+IMIg6D0nYQ==" }, "electron-to-chromium": { - "version": "1.3.50", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.50.tgz", - "integrity": "sha1-dDi3b5K0G5GfP73TUPvQdX2s3fc=" + "version": "1.3.275", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.275.tgz", + "integrity": "sha512-/YWtW/VapMnuYA1lNOaa1F4GhR1LBf+CUTp60lzDPEEh0XOzyOAyULyYZVF9vziZ3qSbTqCwmKwsyRXp66STbw==" }, "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", + "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", "requires": { "bn.js": "^4.4.0", "brorand": "^1.0.1", @@ -2361,6 +3730,12 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -2380,9 +3755,9 @@ } }, "engine.io": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.0.tgz", - "integrity": "sha512-mRbgmAtQ4GAlKwuPnnAvXXwdPhEx+jkc0OBCLrXuD/CRvwNK3AxRSnqK4FSqmAMRRHryVJP8TopOvmEaA64fKw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", + "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", "requires": { "accepts": "~1.3.4", "base64id": "1.0.0", @@ -2392,15 +3767,6 @@ "ws": "~3.3.1" }, "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -2409,17 +3775,14 @@ "ms": "2.0.0" } }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "requires": { - "mime-db": "~1.33.0" + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" } } } @@ -2449,18 +3812,28 @@ "requires": { "ms": "2.0.0" } + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } } } }, "engine.io-parser": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", - "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", + "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", "requires": { "after": "0.8.2", "arraybuffer.slice": "~0.0.7", "base64-arraybuffer": "0.1.5", - "blob": "0.0.4", + "blob": "0.0.5", "has-binary2": "~1.0.2" } }, @@ -2468,18 +3841,25 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "memory-fs": "^0.4.0", "tapable": "^1.0.0" } }, + "env-cmd": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-8.0.2.tgz", + "integrity": "sha512-gHX8MnQXw1iS7dc2KeJdBdxca7spIkxkNwIuORLwm8kDg6xHh5wWnv1Yv3pc64nLZR6kufQSCmwTz16sRmd/rg==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.5" + } + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, "requires": { "prr": "~1.0.1" } @@ -2494,69 +3874,40 @@ } }, "errorhandler": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.0.tgz", - "integrity": "sha1-6rpkyl1UKjEayUX1gt78M2Fl2fQ=", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", "requires": { - "accepts": "~1.3.3", + "accepts": "~1.3.7", "escape-html": "~1.0.3" + }, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + } } }, - "es-abstract": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", - "dev": true, - "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" - } - }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, - "requires": { - "is-callable": "^1.1.1", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" - } - }, - "es5-ext": { - "version": "0.10.45", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz", - "integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } + "es6-promisify": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.0.2.tgz", + "integrity": "sha512-eO6vFm0JvqGzjWIQA6QVKjxpmELfhWbDUWHm1rPfIbn55mhKPiAa5xpLmQWJrNa629ZIeQ8ZvMAi13kvrjK6Mg==" }, "escape-html": { "version": "1.0.3", @@ -2569,48 +3920,210 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.10.0.tgz", - "integrity": "sha512-fjUOf8johsv23WuIKdNQU4P9t9jhQ4Qzx6pC2uW890OloK3Zs1ZAoCNpg/2larNF501jLl3UNy0kIRcF6VI22g==", - "dev": true, + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", "requires": { "esprima": "^3.1.3", "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" + } + }, + "eslint": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.2.1.tgz", + "integrity": "sha512-ES7BzEzr0Q6m5TK9i+/iTpKjclXitOdDK4vT07OqbkBT2/VcN/gO9EL1C4HlK3TAOXYv2ItcmbVR9jO1MR0fJg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.2", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "optional": true + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, + "eslint-loader": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.2.1.tgz", + "integrity": "sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg==", + "dev": true, + "requires": { + "loader-fs-cache": "^1.0.0", + "loader-utils": "^1.0.2", + "object-assign": "^4.0.1", + "object-hash": "^1.1.4", + "rimraf": "^2.6.1" + } + }, "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", + "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.0.0" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.0.tgz", + "integrity": "sha512-boA7CHRLlVWUSg3iL5Kmlt/xT3Q+sXnKoRYYzj1YeM10A76TEJBbotV5pKbnK42hEUIr121zTv+QLRM5LsCPXQ==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz", + "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + } + } + }, "esprima": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } }, "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, "requires": { "estraverse": "^4.1.0" } @@ -2618,8 +4131,7 @@ "estraverse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" }, "esutils": { "version": "2.0.2", @@ -2633,7 +4145,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "requires": { "duplexer": "~0.1.1", @@ -2646,16 +4158,14 @@ } }, "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==" }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -2674,23 +4184,25 @@ "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" - } - }, - "expand-braces": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", - "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", - "requires": { - "array-slice": "^0.2.3", - "array-unique": "^0.2.1", - "braces": "^0.1.2" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } } }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -2705,7 +4217,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -2714,146 +4225,90 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } } } }, - "expand-range": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", - "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "requires": { - "is-number": "^0.1.1", - "repeat-string": "^0.2.2" + "homedir-polyfill": "^1.0.1" } }, + "expect-ct": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz", + "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==" + }, "expose-loader": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-0.7.5.tgz", "integrity": "sha512-iPowgKUZkTPX5PznYsmifVj9Bob0w2wTHVkt/eYNPSzyebkUgIedmskf/kcfEIWpiWjg3JRjnW+a17XypySMuw==" }, "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" + "mime-types": "~2.1.24", + "negotiator": "0.6.2" } }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "~1.33.0" - } + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" } } }, @@ -2868,15 +4323,14 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -2886,18 +4340,27 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } } } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, "requires": { "array-unique": "^0.3.2", "define-property": "^1.0.0", @@ -2909,17 +4372,10 @@ "to-regex": "^3.0.1" }, "dependencies": { - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -2928,7 +4384,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -2937,7 +4392,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -2946,7 +4400,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -2955,18 +4408,11 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -2988,21 +4434,48 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fastparse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" + }, + "feature-policy": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", + "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" + }, + "figures": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz", + "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } }, "file-loader": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", - "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", + "integrity": "sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==", "requires": { "loader-utils": "^1.0.2", - "schema-utils": "^0.4.5" + "schema-utils": "^1.0.0" } }, "filesize": { @@ -3012,115 +4485,116 @@ "dev": true }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - } + "to-regex-range": "^5.0.1" } }, "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } } }, "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^1.0.0", - "pkg-dir": "^2.0.0" + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true }, "flot": { - "version": "0.8.0-alpha", - "resolved": "https://registry.npmjs.org/flot/-/flot-0.8.0-alpha.tgz", - "integrity": "sha1-nLvHFHwQpH0lSduQvSmH7BunhLo=" + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/flot/-/flot-0.8.3.tgz", + "integrity": "sha512-xg2otcTJDvS+ERK+my4wxG/ASq90QURXtoM4LhacCq0jQW2jbyjdttbRNqU2cPykrpMvJ6b2uSp6SAgYAzj9tQ==" }, "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" } }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + } + } }, "forever-agent": { "version": "0.6.1", @@ -3128,25 +4602,13 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" - }, - "dependencies": { - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - } } }, "formidable": { @@ -3164,11 +4626,15 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, "requires": { "map-cache": "^0.2.2" } }, + "frameguard": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz", + "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -3183,7 +4649,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -3193,7 +4658,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", @@ -3207,41 +4671,33 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", - "dev": true, + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" }, "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, + "bundled": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true, + "bundled": true, "optional": true }, "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "dev": true, + "version": "1.1.5", + "bundled": true, "optional": true, "requires": { "delegates": "^1.0.0", @@ -3250,88 +4706,69 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", - "dev": true, + "version": "1.1.1", + "bundled": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true, + "bundled": true, "optional": true }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "version": "4.1.1", + "bundled": true, "optional": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", - "dev": true, + "version": "0.6.0", + "bundled": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, + "bundled": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true, + "bundled": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", - "dev": true, + "bundled": true, "optional": true, "requires": { "minipass": "^2.2.1" @@ -3339,16 +4776,12 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true, + "bundled": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, + "bundled": true, "optional": true, "requires": { "aproba": "^1.0.3", @@ -3362,10 +4795,8 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, + "version": "7.1.3", + "bundled": true, "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -3378,26 +4809,20 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, + "bundled": true, "optional": true }, "iconv-lite": { - "version": "0.4.21", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", - "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", - "dev": true, + "version": "0.4.24", + "bundled": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": ">= 2.1.2 < 3" } }, "ignore-walk": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", - "dev": true, + "bundled": true, "optional": true, "requires": { "minimatch": "^3.0.4" @@ -3405,9 +4830,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, + "bundled": true, "optional": true, "requires": { "once": "^1.3.0", @@ -3416,63 +4839,52 @@ }, "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true, + "bundled": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, + "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, + "bundled": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, + "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "bundled": true, + "optional": true }, "minipass": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", - "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", - "dev": true, + "version": "2.3.5", + "bundled": true, + "optional": true, "requires": { - "safe-buffer": "^5.1.1", + "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", - "dev": true, + "version": "1.2.1", + "bundled": true, "optional": true, "requires": { "minipass": "^2.2.1" @@ -3480,46 +4892,39 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, + "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, + "version": "2.1.1", + "bundled": true, "optional": true }, "needle": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.0.tgz", - "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", - "dev": true, + "version": "2.3.0", + "bundled": true, "optional": true, "requires": { - "debug": "^2.1.2", + "debug": "^4.1.0", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz", - "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", - "dev": true, + "version": "0.12.0", + "bundled": true, "optional": true, "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", - "needle": "^2.2.0", + "needle": "^2.2.1", "nopt": "^4.0.1", "npm-packlist": "^1.1.6", "npmlog": "^4.0.2", - "rc": "^1.1.7", + "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", "tar": "^4" @@ -3527,9 +4932,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "dev": true, + "bundled": true, "optional": true, "requires": { "abbrev": "1", @@ -3537,17 +4940,13 @@ } }, "npm-bundled": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", - "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", - "dev": true, + "version": "1.0.6", + "bundled": true, "optional": true }, "npm-packlist": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", - "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", - "dev": true, + "version": "1.4.1", + "bundled": true, "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -3556,9 +4955,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, + "bundled": true, "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -3569,45 +4966,35 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, + "bundled": true, "optional": true }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, + "once": { + "version": "1.4.0", + "bundled": true, + "optional": true, "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true, + "bundled": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, + "bundled": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, + "bundled": true, "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -3616,26 +5003,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, + "bundled": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true, + "bundled": true, "optional": true }, "rc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", - "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", - "dev": true, + "version": "1.2.8", + "bundled": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", + "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" @@ -3643,18 +5024,14 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true, + "bundled": true, "optional": true } } }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, + "bundled": true, "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -3667,61 +5044,47 @@ } }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, + "version": "2.6.3", + "bundled": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true + "version": "5.1.2", + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, + "bundled": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true, + "bundled": true, "optional": true }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true, + "version": "5.7.0", + "bundled": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true, + "bundled": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true, + "bundled": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, + "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3730,9 +5093,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, + "bundled": true, "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -3740,64 +5101,53 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, + "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, + "bundled": true, "optional": true }, "tar": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.1.tgz", - "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", - "dev": true, + "version": "4.4.8", + "bundled": true, "optional": true, "requires": { - "chownr": "^1.0.1", + "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", + "safe-buffer": "^5.1.2", "yallist": "^3.0.2" } }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true, + "bundled": true, "optional": true }, "wide-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "dev": true, + "version": "1.1.3", + "bundled": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "^1.0.2 || 2" } }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "bundled": true, + "optional": true }, "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", - "dev": true + "version": "3.0.3", + "bundled": true, + "optional": true } } }, @@ -3806,6 +5156,17 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -3815,8 +5176,7 @@ "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getpass": { "version": "0.1.7", @@ -3824,19 +5184,13 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3847,76 +5201,60 @@ } }, "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "is-glob": "^4.0.1" } }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" + "ini": "^1.3.4" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "requires": { + "global-prefix": "^3.0.0" }, "dependencies": { - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "requires": { - "is-extglob": "^2.1.0" + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" } } } }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "dev": true, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "requires": { - "ini": "^1.3.4" + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" } }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -3937,10 +5275,9 @@ } }, "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" }, "growl": { "version": "1.10.5", @@ -3949,94 +5286,33 @@ "dev": true }, "gzip-size": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-4.1.0.tgz", - "integrity": "sha1-iuCWJX6r59acRb4rZ8RIEk/7UXw=", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", "dev": true, "requires": { "duplexer": "^0.1.1", - "pify": "^3.0.0" + "pify": "^4.0.1" }, "dependencies": { "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true } } }, "handlebars": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", - "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", + "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", "dev": true, "requires": { - "async": "^1.4.0", + "neo-async": "^2.6.0", "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "optional": true, - "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true, - "optional": true - } - } - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - } + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" } }, "har-schema": { @@ -4044,12 +5320,13 @@ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "function-bind": "^1.1.1" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" } }, "has-ansi": { @@ -4066,13 +5343,6 @@ "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "requires": { "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - } } }, "has-cors": { @@ -4081,21 +5351,19 @@ "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -4106,7 +5374,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -4116,7 +5383,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -4125,7 +5391,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -4136,7 +5401,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -4147,156 +5411,246 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, "hash.js": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", - "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", - "dev": true, + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, + "hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "dev": true, + "requires": { + "is-stream": "^1.0.1" + } + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "help-me": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-1.1.0.tgz", - "integrity": "sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y=", + "heapdump": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.15.tgz", + "integrity": "sha512-n8aSFscI9r3gfhOcAECAtXFaQ1uy4QSke6bnaL+iymYZ/dWs9cqDqHM+rALfsHUwukUbxsdlECZ0pKmJdQ/4OA==", + "requires": { + "nan": "^2.13.2" + } + }, + "helmet": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.21.1.tgz", + "integrity": "sha512-IC/54Lxvvad2YiUdgLmPlNFKLhNuG++waTF5KPYq/Feo3NNhqMFbcLAlbVkai+9q0+4uxjxGPJ9bNykG+3zZNg==", + "requires": { + "depd": "2.0.0", + "dns-prefetch-control": "0.2.0", + "dont-sniff-mimetype": "1.1.0", + "expect-ct": "0.2.0", + "feature-policy": "0.3.0", + "frameguard": "3.1.0", + "helmet-crossdomain": "0.4.0", + "helmet-csp": "2.9.2", + "hide-powered-by": "1.1.0", + "hpkp": "2.0.0", + "hsts": "2.2.0", + "ienoopen": "1.1.0", + "nocache": "2.1.0", + "referrer-policy": "1.2.0", + "x-xss-protection": "1.3.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "helmet-crossdomain": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz", + "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" + }, + "helmet-csp": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.9.2.tgz", + "integrity": "sha512-Lt5WqNfbNjEJ6ysD4UNpVktSyjEKfU9LVJ1LaFmPfYseg/xPealPfgHhtqdAdjPDopp5zbg/VWCyp4cluMIckw==", "requires": { - "callback-stream": "^1.0.2", - "glob-stream": "^6.1.0", - "through2": "^2.0.1", - "xtend": "^4.0.0" + "bowser": "^2.6.1", + "camelize": "1.0.0", + "content-security-policy-builder": "2.1.0", + "dasherize": "2.0.0" } }, + "hide-powered-by": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz", + "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "dev": true + }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", "dev": true }, - "html-comment-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", - "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=" + "hpkp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", + "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" + }, + "hsts": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", + "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", + "requires": { + "depd": "2.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true }, "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "http2": { + "version": "https://github.com/node-apn/node-http2/archive/apn-2.1.4.tar.gz", + "integrity": "sha512-ad4u4I88X9AcUgxCRW3RLnbh7xHWQ1f5HbrXa7gEy2x4Xgq+rq+auGx5I+nUDE2YYuqteGIlbxrwQXkIaYTfnQ==" + }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" - }, - "icss-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "requires": { - "has-flag": "^3.0.0" - } - } + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=" + }, + "icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "requires": { + "postcss": "^6.0.1" } }, "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", - "dev": true + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ienoopen": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz", + "integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ==" }, "iferr": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "import-fresh": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -4304,37 +5658,47 @@ "dev": true }, "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", "requires": { - "pkg-dir": "^2.0.0", + "pkg-dir": "^3.0.0", "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + } } }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -4352,41 +5716,114 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "inquirer": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz", + "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } }, - "irregular-plurals": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", - "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", - "dev": true + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "loose-envify": "^1.0.0" } }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-arrayish": { @@ -4399,61 +5836,46 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, "requires": { "binary-extensions": "^1.0.0" } }, "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-ci": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", - "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "ci-info": "^1.0.0" + "ci-info": "^1.5.0" } }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -4463,40 +5885,30 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "requires": { "is-extglob": "^2.1.1" } @@ -4511,11 +5923,6 @@ "is-path-inside": "^1.0.0" } }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" - }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -4523,9 +5930,9 @@ "dev": true }, "is-number": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", - "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-obj": { "version": "1.0.1", @@ -4542,43 +5949,26 @@ "path-is-inside": "^1.0.1" } }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "requires": { - "is-unc-path": "^1.0.0" - } - }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -4588,36 +5978,13 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "requires": { - "unc-path-regex": "^0.1.2" - } - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -4626,25 +5993,22 @@ "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" }, "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isstream": { "version": "0.1.2", @@ -4717,6 +6081,12 @@ "path-is-absolute": "^1.0.0" } }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", @@ -4741,19 +6111,148 @@ "requires": { "has-flag": "^1.0.0" } + } + } + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, + "istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "dev": true, + "requires": { + "handlebars": "^4.1.2" + } + }, "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "jquery-ui-bundle": { "version": "1.12.1-migrate", @@ -4765,15 +6264,15 @@ "resolved": "https://registry.npmjs.org/jquery.tooltips/-/jquery.tooltips-1.0.0.tgz", "integrity": "sha1-/Ko2Il0IXQ/NY71E4rAGtUGDHHI=" }, - "js-base64": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz", - "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==" + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" }, "js-storage": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/js-storage/-/js-storage-1.0.4.tgz", - "integrity": "sha512-ND6iVfDo5r2PSpkzXa+s+S6TzZkRD1EaN4NvtwRWp2rrL1pmS9wdcbmv3eYZYOkBcPiQjyQUWZRcT0aQ4Wuk8g==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/js-storage/-/js-storage-1.1.0.tgz", + "integrity": "sha512-XwkyTB3cjwBSaaKo+edR/n8ZbmX/mj5lJpW/O753NYvMpClQeurucceIvX3HeF4ZTTY2YRPXTVzgPByK4pA7aQ==" }, "js-tokens": { "version": "3.0.2", @@ -4781,9 +6280,9 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.0.tgz", - "integrity": "sha512-0LoUNELX4S+iofCT8f4uEHIiRBR+c2AINyC8qRWfC6QNruLtxVZRJaPcu/xwMgFIgDxF25tGHaDjvxzJCNE9yw==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -4791,9 +6290,9 @@ }, "dependencies": { "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true } } @@ -4801,14 +6300,12 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.11.0.tgz", "integrity": "sha512-ou1VyfjwsSuWkudGxb03FotDajxAto6USAlmMZjE2lc0jCznt7sBWkhfRBRaWwbnmDqdMSTKTLT5d9sBFkkM7A==", - "dev": true, "requires": { "abab": "^1.0.4", "acorn": "^5.3.0", @@ -4836,127 +6333,6 @@ "whatwg-url": "^6.4.1", "ws": "^4.0.0", "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "abab": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", - "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", - "dev": true - }, - "acorn": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", - "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", - "dev": true - }, - "acorn-globals": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz", - "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", - "dev": true, - "requires": { - "acorn": "^5.0.0" - } - }, - "cssstyle": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.3.1.tgz", - "integrity": "sha512-tNvaxM5blOnxanyxI6panOsnfiyLRj3HV4qjqqS45WPNS1usdYWRUQjqTEEELK73lpeP/1KoIGYUwrBn/VcECA==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "request-promise-native": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", - "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", - "dev": true, - "requires": { - "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz", - "integrity": "sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.19" - } - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "ws": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", - "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - } } }, "jsesc": { @@ -4967,8 +6343,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema": { "version": "0.2.3", @@ -4980,13 +6355,11 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "json-stable-stringify": { + "json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "~0.0.0" - } + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", @@ -4994,21 +6367,19 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } }, "jsonwebtoken": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", - "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", "requires": { - "jws": "^3.1.5", + "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -5016,13 +6387,19 @@ "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", - "ms": "^2.1.1" + "ms": "^2.1.1", + "semver": "^5.6.0" }, "dependencies": { "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" } } }, @@ -5035,42 +6412,31 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "jwa": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", - "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", "requires": { "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", + "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "jws": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", - "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", "requires": { - "jwa": "^1.1.5", + "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" }, "latest-version": { "version": "3.1.0", @@ -5081,29 +6447,23 @@ "package-json": "^4.0.0" } }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } }, "left-pad": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz", - "integrity": "sha1-kUS27ryl8dBoAWnxpncNzqYLdcM=" + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -5121,52 +6481,61 @@ "strip-bom": "^3.0.0" } }, + "loader-fs-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz", + "integrity": "sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw==", + "dev": true, + "requires": { + "find-cache-dir": "^0.1.1", + "mkdirp": "0.5.1" + } + }, "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", - "dev": true + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" }, "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", "requires": { - "big.js": "^3.1.3", + "big.js": "^5.2.2", "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "json5": "^1.0.1" } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, "lodash.includes": { @@ -5199,11 +6568,6 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -5212,89 +6576,14 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "loglevelnext": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.5.tgz", - "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", - "dev": true, - "requires": { - "es6-symbol": "^3.1.1", - "object.assign": "^4.1.0" - } - }, - "long": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "js-tokens": "^3.0.0 || ^4.0.0" } }, "lowercase-keys": { @@ -5304,9 +6593,9 @@ "dev": true }, "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -5325,20 +6614,20 @@ "mamacro": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", - "dev": true + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==" + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", - "dev": true + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-stream": { "version": "0.1.0", @@ -5349,76 +6638,71 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, "requires": { "object-visit": "^1.0.0" } }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=" + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } }, "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", - "dev": true, + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "requires": { "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "meant": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.1.tgz", - "integrity": "sha512-UakVLFjKkbbUwNWJ2frVLnnAtbb7D7DsloxRd3s/gDpI8rdv8W5Hp3NaDb+POBI1fQdeussER6NB8vpcRURvlg==", - "dev": true - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "memory-cache": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", + "integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo=" + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" } }, - "meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "merge-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", - "integrity": "sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==", + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "is-plain-obj": "^1.1" + "source-map": "^0.6.1" } }, "methods": { @@ -5430,7 +6714,6 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -5447,46 +6730,80 @@ "to-regex": "^3.0.2" }, "dependencies": { - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } } } }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, @@ -5494,81 +6811,106 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" } }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==" + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" }, "mime-db": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "requires": { - "mime-db": "~1.27.0" + "mime-db": "1.40.0" } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimed-connect-to-nightscout": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/minimed-connect-to-nightscout/-/minimed-connect-to-nightscout-1.1.1.tgz", - "integrity": "sha512-Cts3oJPtLdRCOeBRu5tqoIRoZjAsYuNN4LhAdtZG8P42MJxFZdsRDc13BdDNEq7GCxhVcNtvan3XwTnobm0xFg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/minimed-connect-to-nightscout/-/minimed-connect-to-nightscout-1.3.2.tgz", + "integrity": "sha512-1fF1ekFafvzNB9VzoP5T74KVJ32UDi9s4IE8ezNGOJ1XUAAq3qgWYGC4tZgug51sbMvr7c77XQw0bIPl2RpU9Q==", "requires": { - "common": "0.2.x", - "lodash": "^4.17.10", - "request": "^2.87.0" + "common": "^0.2.5", + "lodash": "^4.17.15", + "request": "^2.88.0" }, "dependencies": { "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==" + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" } }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -5586,9 +6928,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "balanced-match": { "version": "1.0.0", @@ -5599,7 +6941,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, "requires": { "tweetnacl": "^0.14.3" } @@ -5618,29 +6959,72 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" - }, "common": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/common/-/common-0.2.5.tgz", @@ -5656,6 +7040,18 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -5665,11 +7061,24 @@ } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" } }, "delayed-stream": { @@ -5683,12 +7092,48 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "requires": { - "jsbn": "~0.1.0" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, "escape-string-regexp": { @@ -5696,15 +7141,34 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "expect.js": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz", "integrity": "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=" }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extsprintf": { "version": "1.3.0", @@ -5712,27 +7176,43 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "requires": { + "is-buffer": "~2.0.3" + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -5741,6 +7221,24 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -5750,9 +7248,9 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5773,23 +7271,36 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "^5.1.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "http-signature": { "version": "1.2.0", @@ -5811,25 +7322,84 @@ } }, "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "is-buffer": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "requires": { + "has-symbols": "^1.0.0" + } }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "json-schema": { "version": "0.2.3", @@ -5837,9 +7407,9 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stringify-safe": { "version": "5.0.1", @@ -5857,24 +7427,72 @@ "verror": "1.10.0" } }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "requires": { + "chalk": "^2.0.1" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.38.0" } }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -5897,55 +7515,189 @@ } }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", + "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", "requires": { + "ansi-colors": "3.2.3", "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", + "debug": "3.2.6", "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.2", + "find-up": "3.0.0", + "glob": "7.1.3", "growl": "1.10.5", - "he": "1.1.1", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "5.4.0" + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + }, + "p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "requires": { + "p-try": "^2.0.0" + } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { - "wrappy": "1" + "p-limit": "^2.0.0" } }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", @@ -5953,32 +7705,48 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" + }, + "dependencies": { + "node-uuid": { + "version": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", + "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=" + } } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -5989,10 +7757,43 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -6005,20 +7806,55 @@ "tweetnacl": "~0.14.0" } }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "requires": { "has-flag": "^3.0.0" } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } } }, "tunnel-agent": { @@ -6032,8 +7868,15 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } }, "uuid": { "version": "3.3.2", @@ -6050,34 +7893,192 @@ "extsprintf": "^1.2.0" } }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } } } }, "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "mississippi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", - "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "requires": { "concat-stream": "^1.5.0", "duplexify": "^3.4.2", @@ -6085,29 +8086,16 @@ "flush-write-stream": "^1.0.0", "from2": "^2.1.0", "parallel-transform": "^1.1.0", - "pump": "^2.0.1", + "pump": "^3.0.0", "pumpify": "^1.3.3", "stream-each": "^1.1.0", "through2": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } } }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -6117,7 +8105,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -6173,12 +8160,6 @@ "ms": "2.0.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -6191,42 +8172,101 @@ } }, "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "moment-locales-webpack-plugin": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/moment-locales-webpack-plugin/-/moment-locales-webpack-plugin-1.0.7.tgz", - "integrity": "sha512-KjYpaAhmuzGFZl6534FlZoK7QtW3vqlxd+A17W9DlgHJ5yhXANy7AZJl7iYtZpWjAfMTAWiVrQ7YDZdkFO6uRw==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/moment-locales-webpack-plugin/-/moment-locales-webpack-plugin-1.1.0.tgz", + "integrity": "sha512-0Hn+xdNmQt+XZgsWOlwXJcQ881nURSoDJY1o4hOLiyGaUVZbY475GrvyBXUOMc5mgjvPiQz/KU8ht/IoRnadMg==", "requires": { "lodash.difference": "^4.5.0" } }, "moment-timezone": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz", - "integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==", + "version": "0.5.26", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.26.tgz", + "integrity": "sha512-sFP4cgEKTCymBBKgoxZjYzlSovC20Y6J7y3nanDc5RoBIXKlZhoYwBoZGe3flwU6A372AcRwScH8KiwV6zjy1g==", "requires": { "moment": ">= 2.9.0" } }, - "mongodb": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.0.11.tgz", - "integrity": "sha512-60KV+DPW++fzaD5cYbieCRQcXiYWQdRLHBqQyuu3rJmrP8vYZgI4u5UwfsUX6nGLON69FUIu4d3tH+WL4jShuA==", + "moment-timezone-data-webpack-plugin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/moment-timezone-data-webpack-plugin/-/moment-timezone-data-webpack-plugin-1.1.0.tgz", + "integrity": "sha512-szKf9rbRoY9u3WNcwK6D0tdCREk/OZkcF1k163Xc5m7GcqBh28LgNVWisb4sje6/qdG3WUkFD5hJ7/lmKOkBAA==", "requires": { - "mongodb-core": "3.0.11" + "find-cache-dir": "^3.0.0", + "make-dir": "^3.0.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.0.0.tgz", + "integrity": "sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.0", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + } } }, - "mongodb-core": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.0.11.tgz", - "integrity": "sha512-agzBbSP3ahEYJyBMQicj70B+n+NNYsKaroezu5ETcImXs9nqf89/QI6e8iipg2rY3a8OWZBHWvqYxnsWasrUQA==", + "mongodb": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.2.tgz", + "integrity": "sha512-fqJt3iywelk4yKu/lfwQg163Bjpo5zDKhXiohycvon4iQHbrfflSAz9AIlRE6496Pm/dQKQK5bMigdVo2s6gBg==", "requires": { - "bson": "~1.0.4", - "require_optional": "^1.0.1" + "bson": "^1.1.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2" } }, "mongomock": { @@ -6241,86 +8281,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, "requires": { "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "mqtt": { - "version": "2.18.3", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-2.18.3.tgz", - "integrity": "sha512-BXCUugFgA6FOWJGxhvUWtVLOdt6hYTmiMGPksEyKuuF1FQ0ji7UJBJ/0kVRMUtUWCAtPGnt4mZZZgJpzNLcuQg==", - "requires": { - "commist": "^1.0.0", - "concat-stream": "^1.6.2", - "end-of-stream": "^1.4.1", - "help-me": "^1.0.1", - "inherits": "^2.0.3", - "minimist": "^1.2.0", - "mqtt-packet": "^5.6.0", - "pump": "^3.0.0", - "readable-stream": "^2.3.6", - "reinterval": "^1.1.0", - "split2": "^2.1.1", - "websocket-stream": "^5.1.2", - "xtend": "^4.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "mqtt-packet": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-5.6.0.tgz", - "integrity": "sha512-QECe2ivqcR1LRsPobRsjenEKAC3i1a5gmm+jNKJLrsiq9PaSQ18LlKFuxvhGxWkvGEPadWv6rKd31O4ICqS1Xw==", - "requires": { - "bl": "^1.2.1", - "inherits": "^2.0.3", - "process-nextick-args": "^2.0.0", - "safe-buffer": "^5.1.0" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - } + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" } }, "ms": { @@ -6328,18 +8295,21 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true, - "optional": true + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -6352,60 +8322,58 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" - }, - "dependencies": { - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } } }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "neo-async": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", - "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==", - "dev": true + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "nocache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", + "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + }, "node-cache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.0.tgz", - "integrity": "sha512-obRu6/f7S024ysheAjoYFEEBqqDWv4LOMNJEuO8vMeEw2AT4z+NCzO4hlc2lhI4vATzbCQv6kke9FVdx0RbCOw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.1.tgz", + "integrity": "sha512-BOb67bWg2dTyax5kdef5WfU3X8xu4wPg+zHzkvls0Q/QpYycIFRLEEIdAx9Wma43DxG6Qzn4illdZoYseKWa4A==", "requires": { "clone": "2.x", - "lodash": "4.x" - }, - "dependencies": { - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=" - } + "lodash": "^4.17.15" } }, + "node-forge": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", + "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==" + }, "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", - "dev": true, + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", "requires": { "assert": "^1.1.1", "browserify-zlib": "^0.2.0", @@ -6414,10 +8382,10 @@ "constants-browserify": "^1.0.0", "crypto-browserify": "^3.11.0", "domain-browser": "^1.1.1", - "events": "^1.0.0", + "events": "^3.0.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", - "path-browserify": "0.0.0", + "path-browserify": "0.0.1", "process": "^0.11.10", "punycode": "^1.2.4", "querystring-es3": "^0.2.0", @@ -6428,8 +8396,73 @@ "timers-browserify": "^2.0.4", "tty-browserify": "0.0.0", "url": "^0.11.0", - "util": "^0.10.3", - "vm-browserify": "0.0.4" + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "node-releases": { + "version": "1.1.34", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.34.tgz", + "integrity": "sha512-fNn12JTEfniTuCqo0r9jXgl44+KxRH/huV7zM/KAGOKxDKrHr6EbT7SSs4B+DNxyBE2mks28AD+Jw6PkfY5uwA==", + "requires": { + "semver": "^6.3.0" + } + }, + "nodemon": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", + "integrity": "sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==", + "dev": true, + "requires": { + "chokidar": "^2.1.5", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.6", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "nopt": { @@ -6442,73 +8475,152 @@ } }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" }, "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true } } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, "requires": { "path-key": "^2.0.0" } }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" - }, "nwsapi": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.4.tgz", - "integrity": "sha512-Zt6HRR6RcJkuj5/N9zeE7FN6YitRW//hK2wTOwX274IBphbY3Zf5+yn5mZ9v/SzAOTMjQNxZf9KkmPLWn0cV4g==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.3.tgz", + "integrity": "sha512-RowAaJGEgYXEZfQ7tvvdtAQUKPyTR6T6wNu0fwlNsGQYr/h3yQc6oI8WnVZh3Y/Sylwc+dtAlvPqfFZjhTyk3A==" + }, + "nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-component": { "version": "0.0.3", @@ -6519,7 +8631,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -6530,24 +8641,35 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } } } }, - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", "dev": true }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, "requires": { "isobject": "^3.0.0" } @@ -6556,7 +8678,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, "requires": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", @@ -6568,23 +8689,10 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, "requires": { "isobject": "^3.0.1" } }, - "object.values": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", - "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" - } - }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -6594,9 +8702,9 @@ } }, "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" }, "once": { "version": "1.4.0", @@ -6607,34 +8715,48 @@ } }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "^2.1.0" } }, "opener": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", - "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", "dev": true }, - "opn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "is-wsl": "^1.1.0" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } } }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.4", @@ -6642,130 +8764,105 @@ "prelude-ls": "~1.1.2", "type-check": "~0.3.2", "wordwrap": "~1.0.0" - }, - "dependencies": { - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - } } }, - "optjs": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", - "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4=" + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, - "ora": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-2.1.0.tgz", - "integrity": "sha512-hNNlAd3gfv/iPmsNxYoAPLvxg7HuPozww7fFonMZvL84tP6Ox5igfk5j/+a9rtJJwqMgKK+JgWsAQik5o0HTLA==", - "dev": true, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "requires": { - "chalk": "^2.3.1", - "cli-cursor": "^2.1.0", - "cli-spinners": "^1.1.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^4.0.0", - "wcwidth": "^1.0.1" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "requires": { - "has-flag": "^3.0.0" + "pump": "^3.0.0" } } } }, - "ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", - "requires": { - "readable-stream": "^2.0.1" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } }, "package-json": { "version": "4.0.1", @@ -6777,36 +8874,51 @@ "registry-auth-token": "^3.0.1", "registry-url": "^3.0.3", "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==" }, "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "dev": true, + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", "requires": { - "cyclist": "~0.2.2", + "cyclist": "^1.0.1", "inherits": "^2.0.3", "readable-stream": "^2.1.5" } }, - "parse-asn1": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, "parse-duration": { @@ -6824,6 +8936,16 @@ "json-parse-better-errors": "^1.0.1" } }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" + }, "parseqs": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", @@ -6841,21 +8963,19 @@ } }, "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", - "dev": true + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" }, "path-dirname": { "version": "1.0.2", @@ -6863,10 +8983,13 @@ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } }, "path-is-absolute": { "version": "1.0.1", @@ -6882,14 +9005,12 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -6914,10 +9035,9 @@ } }, "pbkdf2": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", - "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", - "dev": true, + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -6926,400 +9046,70 @@ "sha.js": "^2.4.8" } }, + "pem": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/pem/-/pem-1.14.3.tgz", + "integrity": "sha512-Q+AMVMD3fzeVvZs5PHeI+pVt0hgZY2fjhkliBW43qyONLgCXPVk1ryim43F9eupHlNGLJNT5T/NNrzhUdiC5Zg==", + "requires": { + "es6-promisify": "^6.0.0", + "md5": "^2.2.1", + "os-tmpdir": "^1.0.1", + "which": "^1.3.1" + } + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "plur": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/plur/-/plur-3.0.1.tgz", - "integrity": "sha512-lJl0ojUynAM1BZn58Pas2WT/TXeC1+bS+UqShl0x9+49AtOn7DixRXVzaC8qrDOIxNDmepKnLuMTH7NQmkX0PA==", - "dev": true, - "requires": { - "irregular-plurals": "^2.0.0" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - }, - "dependencies": { - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", - "requires": { - "postcss": "^5.0.2", - "postcss-message-helpers": "^2.0.0", - "reduce-css-calc": "^1.2.6" - } - }, - "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", - "requires": { - "colormin": "^1.0.5", - "postcss": "^5.0.13", - "postcss-value-parser": "^3.2.3" - } - }, - "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", - "requires": { - "postcss": "^5.0.11", - "postcss-value-parser": "^3.1.2" - } - }, - "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", - "requires": { - "postcss": "^5.0.14" - } - }, - "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", - "requires": { - "postcss": "^5.0.14" - } - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", - "requires": { - "postcss": "^5.0.16" - } - }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", - "requires": { - "postcss": "^5.0.14", - "uniqs": "^2.0.0" - } - }, - "postcss-filter-plugins": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz", - "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==", - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.10", - "postcss-value-parser": "^3.1.1" - } - }, - "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", - "requires": { - "browserslist": "^1.5.2", - "caniuse-api": "^1.5.2", - "postcss": "^5.0.4", - "postcss-selector-parser": "^2.2.2", - "vendors": "^1.0.0" - } - }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=" - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", - "requires": { - "object-assign": "^4.0.1", - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.2" - }, - "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - } - } - }, - "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", - "requires": { - "postcss": "^5.0.12", - "postcss-value-parser": "^3.3.0" - } - }, - "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", - "requires": { - "alphanum-sort": "^1.0.1", - "postcss": "^5.0.2", - "postcss-value-parser": "^3.0.2", - "uniqs": "^2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", - "requires": { - "alphanum-sort": "^1.0.2", - "has": "^1.0.1", - "postcss": "^5.0.14", - "postcss-selector-parser": "^2.0.0" - } - }, - "postcss-modules-extract-imports": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz", - "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "requires": { - "has-flag": "^3.0.0" - } - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" } }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "requires": { - "has-flag": "^3.0.0" - } - } + "find-up": "^1.0.0" } }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" }, "dependencies": { "ansi-styles": { @@ -7331,224 +9121,115 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } } } }, - "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", - "requires": { - "postcss": "^5.0.5" - } - }, - "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^1.4.0", - "postcss": "^5.0.14", - "postcss-value-parser": "^3.2.3" - } - }, - "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", - "requires": { - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.1" - } - }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "requires": { - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.2" - } - }, - "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", + "postcss-modules-extract-imports": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", + "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", "requires": { - "has": "^1.0.1", - "postcss": "^5.0.8", - "postcss-value-parser": "^3.0.1" + "postcss": "^6.0.1" } }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" } }, - "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", + "postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", "requires": { - "is-svg": "^2.0.0", - "postcss": "^5.0.14", - "postcss-value-parser": "^3.2.3", - "svgo": "^0.7.0" + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" } }, - "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", + "postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", "requires": { - "alphanum-sort": "^1.0.1", - "postcss": "^5.0.4", - "uniqs": "^2.0.0" + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" } }, "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=" - }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.4", - "uniqs": "^2.0.0" - } + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "pretty-bytes": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.1.0.tgz", - "integrity": "sha512-wa5+qGVg9Yt7PB6rYm3kXlKzgzgivYTLRandezh43jjRqgyDyP+9YxfJpJiLs9yKD1WeU8/OvtToWpW7255FtA==", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, - "prettyjson": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prettyjson/-/prettyjson-1.2.1.tgz", - "integrity": "sha1-/P+rQdGcq0365eV15kJGYZsS0ok=", - "requires": { - "colors": "^1.1.2", - "minimist": "^1.2.0" - }, - "dependencies": { - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "protobufjs": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-3.2.4.tgz", - "integrity": "sha1-k/kbBym7yHTtywAr3xrY+OxXAdo=", - "requires": { - "ascli": "^1.0.1", - "bytebuffer": "~3.1" - } + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" + "ipaddr.js": "1.9.0" } }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, "pseudomap": { "version": "1.0.2", @@ -7557,22 +9238,27 @@ "dev": true }, "psl": { - "version": "1.1.28", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.28.tgz", - "integrity": "sha512-+AqO1Ae+N/4r7Rvchrdm432afjT9hqJRyBN3DQv9At0tPz4hIFSGKbq64fN9dVoCow4oggIIax5/iONx0r9hZw==", + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", "dev": true }, "public-encrypt": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", - "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1" + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "pump": { @@ -7606,59 +9292,29 @@ } }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "pushover-notifications": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/pushover-notifications/-/pushover-notifications-0.2.4.tgz", - "integrity": "sha1-icuU+RgsNqpZqaxnROSa2K/00Gc=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pushover-notifications/-/pushover-notifications-1.2.1.tgz", + "integrity": "sha512-FEPbbEhKPDw4PP/e4irEEv1gRmHvt2rulpsvj9OaWTBLWuTf0qBEuaydOsYnQdXS7zq0fAX/ptsj5/BqbKrcUw==" }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - } - } + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", - "dev": true + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" }, "random-token": { "version": "0.0.8", @@ -7666,10 +9322,9 @@ "integrity": "sha1-HPhFrz+zHlf3yqS5oXNHjEZIO2E=" }, "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "requires": { "safe-buffer": "^5.1.0" } @@ -7678,35 +9333,31 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, "requires": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" } }, "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "dependencies": { - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" } } }, @@ -7722,10 +9373,10 @@ "strip-json-comments": "~2.0.1" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true } } @@ -7742,98 +9393,98 @@ } }, "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", "dev": true, "requires": { - "find-up": "^2.0.0", + "find-up": "^3.0.0", "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + } } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", + "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" - } - }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "dev": true, - "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "requires": { - "balanced-match": "^0.4.2", - "math-expression-evaluator": "^1.2.14", - "reduce-function-call": "^1.0.1" }, "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" } } }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "requires": { - "balanced-match": "^0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" - } + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, + "referrer-policy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", + "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" }, + "regenerate-unicode-properties": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", + "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-transform": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", + "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", + "requires": { + "private": "^0.1.6" + } + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" } }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, "regexpu-core": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", @@ -7845,9 +9496,9 @@ } }, "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "dev": true, "requires": { "rc": "^1.1.6", @@ -7876,10 +9527,14 @@ "jsesc": "~0.5.0" } }, - "reinterval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", - "integrity": "sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc=" + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } }, "remove-trailing-separator": { "version": "1.1.0", @@ -7887,165 +9542,86 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" }, "repeat-string": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", - "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" }, "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - }, - "dependencies": { - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "~1.0.0" - } - } - } - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "~1.33.0" - } - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } } } }, "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", - "dev": true, + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", "requires": { - "lodash": "^4.13.1" + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "require_optional": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", @@ -8053,22 +9629,32 @@ "requires": { "resolve-from": "^2.0.0", "semver": "^5.1.0" + }, + "dependencies": { + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } } }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "dev": true, + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "requires": { - "path-parse": "^1.0.5" + "path-parse": "^1.0.6" } }, "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, "requires": { "resolve-from": "^3.0.0" }, @@ -8076,37 +9662,56 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } } } }, "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "rewire": { "version": "2.5.2", @@ -8114,54 +9719,78 @@ "integrity": "sha1-ZCfee3/u+n02QBUH62SlOFvFjcc=", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, - "requires": { - "align-text": "^0.1.1" - } - }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" } }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, "requires": { "aproba": "^1.1.1" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, + "rxjs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, "requires": { "ret": "~0.1.10" } @@ -8174,21 +9803,22 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=" + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", "ajv-keywords": "^3.1.0" } }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "semver-diff": { "version": "2.1.0", @@ -8197,12 +9827,20 @@ "dev": true, "requires": { "semver": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -8211,54 +9849,51 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "range-parser": "~1.2.1", + "statuses": "~1.5.0" }, "dependencies": { "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, "serialize-javascript": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", - "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", - "dev": true + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" }, "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "parseurl": "~1.3.3", + "send": "0.17.1" } }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -8270,7 +9905,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -8280,78 +9914,48 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "sgvdata": { - "version": "git://github.com/ktind/sgvdata.git#150873bc92e80bf645796ee19f13c6cff79d5630", - "from": "git://github.com/ktind/sgvdata.git#wip/protobuf", - "requires": { - "event-stream": "~3.1.5", - "protobufjs": "~3.2.0" - }, - "dependencies": { - "event-stream": { - "version": "3.1.7", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.1.7.tgz", - "integrity": "sha1-tMVAAS0P4UmEIPPYlGAI22OTw3o=", - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.2", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "split": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", - "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", - "requires": { - "through": "2" - } - } - } + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" } }, "share2nightscout-bridge": { - "version": "git://github.com/nightscout/share2nightscout-bridge.git#d4a0d75db30461fee3131bd4efd66326c63496e6", - "from": "git://github.com/nightscout/share2nightscout-bridge.git#wip/generalize", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/share2nightscout-bridge/-/share2nightscout-bridge-0.2.1.tgz", + "integrity": "sha512-tzMlJHYNysMsPgCwbG0y+hHA6XtYtpZ13YCoXQ6aQqxsn8fln/JUkJU8jCt1LfeMkvY2BAq+d4oek4r0aACQoQ==", "requires": { - "request": "^2.87.0" + "request": "^2.88.0" }, "dependencies": { "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -8369,15 +9973,14 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, "requires": { "tweetnacl": "^0.14.3" } @@ -8387,15 +9990,10 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { "delayed-stream": "~1.0.0" } @@ -8419,12 +10017,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "extend": { @@ -8438,9 +10036,9 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -8453,12 +10051,12 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -8476,11 +10074,11 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "^5.1.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, @@ -8507,8 +10105,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "json-schema": { "version": "0.2.3", @@ -8516,9 +10113,9 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stringify-safe": { "version": "5.0.1", @@ -8537,32 +10134,37 @@ } }, "mime-db": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "~1.35.0" + "mime-db": "~1.37.0" } }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", @@ -8570,30 +10172,30 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" } }, "safe-buffer": { @@ -8607,9 +10209,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz", + "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -8623,11 +10225,19 @@ } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } } }, "tunnel-agent": { @@ -8641,8 +10251,15 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } }, "uuid": { "version": "3.3.2", @@ -8665,7 +10282,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -8673,18 +10289,17 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shiro-trie": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/shiro-trie/-/shiro-trie-0.3.13.tgz", - "integrity": "sha1-pKZrVJjWm5RgLnqYsAVzFbO1x0Q=" + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/shiro-trie/-/shiro-trie-0.4.8.tgz", + "integrity": "sha512-CtBcRIbueg6dnSE1XuFf03RX67GdSUFcb5+zhbTL+cESTd8Vjr42YvtHMKlOZvsLt0RmtbuLzslLix8E4nKFwg==" }, "should": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.2.tgz", - "integrity": "sha512-3y3b/1GsvXZU2rMVDOGtaeX8N0KhdtiaGG6IhMsVkZyjcR0PokdPS9UUmlnYj5h94+ECqUr+Z6KYTm846A/tfQ==", + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", "dev": true, "requires": { "should-equal": "^2.0.0", @@ -8738,19 +10353,45 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-statistics": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/simple-statistics/-/simple-statistics-0.7.0.tgz", "integrity": "sha1-xQ8Tu9yX6aT9ICVjgtoX5o7asco=" }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -8766,7 +10407,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -8775,10 +10415,14 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -8786,7 +10430,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -8797,7 +10440,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -8806,7 +10448,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -8815,7 +10456,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -8824,18 +10464,11 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -8843,9 +10476,18 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, "requires": { "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "socket.io": { @@ -8924,37 +10566,23 @@ "requires": { "ms": "2.0.0" } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" } } }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "requires": { - "is-plain-obj": "^1.0.0" - } - }, "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, "requires": { "atob": "^2.1.1", "decode-uri-component": "^0.2.0", @@ -8963,16 +10591,38 @@ "urix": "^0.1.0" } }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spawn-wrap": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", + "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -8980,9 +10630,9 @@ } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, "spdx-expression-parse": { @@ -8996,9 +10646,9 @@ } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, "split": { @@ -9013,28 +10663,20 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, "requires": { "extend-shallow": "^3.0.0" } }, - "split2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", - "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", - "requires": { - "through2": "^2.0.2" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -9045,34 +10687,20 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" - }, - "dependencies": { - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "ssri": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", - "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", - "dev": true, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "requires": { - "safe-buffer": "^5.1.1" + "figgy-pudding": "^3.5.1" } }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -9082,7 +10710,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -9097,14 +10724,12 @@ "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" @@ -9116,20 +10741,12 @@ "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "requires": { "duplexer": "~0.1.1" - }, - "dependencies": { - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" - } } }, "stream-each": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", - "dev": true, + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", "requires": { "end-of-stream": "^1.1.0", "stream-shift": "^1.0.0" @@ -9139,88 +10756,51 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, "requires": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.1", "readable-stream": "^2.3.6", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz", + "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^5.2.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -9242,34 +10822,27 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, "style-loader": { - "version": "0.20.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.20.3.tgz", - "integrity": "sha512-2I7AVP73MvK33U7B9TKlYZAqdROyMXDYSMvHLX43qy3GCOaJNiV6i0v/sv9idWIaQ42Yn2dNv79Q5mKXbKhAZg==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", "requires": { "loader-utils": "^1.1.0", - "schema-utils": "^0.4.5" + "schema-utils": "^1.0.0" } }, "superagent": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", - "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", "dev": true, "requires": { "component-emitter": "^1.2.0", @@ -9277,20 +10850,20 @@ "debug": "^3.1.0", "extend": "^3.0.0", "form-data": "^2.3.1", - "formidable": "^1.1.1", + "formidable": "^1.2.0", "methods": "^1.1.1", "mime": "^1.4.1", "qs": "^6.5.1", - "readable-stream": "^2.0.5" + "readable-stream": "^2.3.5" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "mime": { @@ -9298,17 +10871,23 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, "supertest": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.1.0.tgz", - "integrity": "sha512-O44AMnmJqx294uJQjfUmEyYOg7d9mylNFsMw/Wkz4evKd1njyPrtCN+U6ZIC7sKtfEVQhfTqFFijlXx8KP/Czw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", + "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", "dev": true, "requires": { - "methods": "~1.1.2", - "superagent": "3.8.2" + "methods": "^1.1.2", + "superagent": "^3.8.3" } }, "supports-color": { @@ -9316,52 +10895,92 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, - "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", - "requires": { - "coa": "~1.0.1", - "colors": "~1.1.2", - "csso": "~2.3.1", - "js-yaml": "~3.7.0", - "mkdirp": "~0.5.1", - "sax": "~1.2.1", - "whet.extend": "~0.9.9" + "swagger-ui-dist": { + "version": "3.23.11", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.23.11.tgz", + "integrity": "sha512-ipENHHH/sqpngTpHXUwg55eAOZ7b2UVayUwwuWPA6nQSPhjBVXX4zOPpNKUwQIFOl3oIwVvZF7mqoxH7pMgVzA==" + }, + "swagger-ui-express": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.2.tgz", + "integrity": "sha512-bVT16qj6WdNlEKFkSLOoTeGuqEm2lfOFRq6mVHAx+viA/ikORE+n4CS3WpVcYmQzM4HE6+DUFgAWcMRBJNpjcw==", + "requires": { + "swagger-ui-dist": "^3.18.1" + } + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true }, - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^2.6.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" } } } }, - "symbol-tree": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", - "dev": true - }, "tapable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", - "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", - "dev": true + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, "term-size": { "version": "1.2.0", @@ -9372,6 +10991,117 @@ "execa": "^0.7.0" } }, + "terser": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", + "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "requires": { + "commander": "^2.19.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.10" + } + }, + "terser-webpack-plugin": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", + "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^2.1.2", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "terser": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.2.tgz", + "integrity": "sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ==", + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + } + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -9384,23 +11114,14 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "requires": { - "readable-stream": "^2.1.5", + "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, - "through2-filter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", - "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", @@ -9408,27 +11129,20 @@ "dev": true }, "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", - "dev": true, + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", "requires": { - "setimmediate": "^1.0.4" - } - }, - "titleize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/titleize/-/titleize-1.0.1.tgz", - "integrity": "sha512-rUwGDruKq1gX+FFHbTl5qjI7teVO7eOe+C8IcQ7QT+1BK3eEUXJqbZcBOeaRP4FwSC/C1A5jDoIVta0nIQ9yew==", - "dev": true + "setimmediate": "^1.0.4" + } }, - "to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "os-tmpdir": "~1.0.2" } }, "to-array": { @@ -9439,23 +11153,35 @@ "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -9464,40 +11190,53 @@ } }, "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "nopt": "~1.0.10" }, "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "abbrev": "1" } - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true } } }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "punycode": "^2.1.0" } }, "traverse": { @@ -9505,11 +11244,10 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", - "dev": true + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, "tryer": { "version": "1.0.1", @@ -9518,54 +11256,49 @@ "dev": true }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, "requires": { "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", + "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==", + "dev": true + }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" - }, - "dependencies": { - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "~1.33.0" - } - } + "mime-types": "~2.1.24" } }, "typedarray": { @@ -9574,150 +11307,79 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "uglify-js": { - "version": "3.4.6", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.6.tgz", - "integrity": "sha512-O1D7L6WcOzS1qW2ehopEm4cWm5yA6bQBozlks8jO8ODxYCy4zv+bR/la4Lwp01tpkYGNonnpXvUpYtrvSu8Yzg==", + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.6.tgz", + "integrity": "sha512-YDKRX8F0Y+Jr7LhoVk0n4G7ltR3Y7qFAj+DtVBthlOgCcIj1hyMigCfousVfn9HKmvJ+qiFlLDwaHx44/e5ZKw==", "requires": { - "commander": "~2.16.0", + "commander": "~2.20.0", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" }, - "uglifyjs-webpack-plugin": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz", - "integrity": "sha512-1VicfKhCYHLS8m1DCApqBhoulnASsEoJ/BvpUpP4zoNAPpKzdH+ghk0olGJMmwX2/jprK2j3hAHdUbczBSy2FA==", + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", "dev": true, "requires": { - "cacache": "^10.0.4", - "find-cache-dir": "^1.0.0", - "schema-utils": "^0.4.5", - "serialize-javascript": "^1.4.0", - "source-map": "^0.6.1", - "uglify-es": "^3.3.4", - "webpack-sources": "^1.1.0", - "worker-farm": "^1.5.2" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "dev": true, - "requires": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - } - } + "debug": "^2.2.0" } }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==" }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" - }, "unique-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", - "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", - "dev": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "requires": { "unique-slug": "^2.0.0" } }, "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", - "dev": true, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", "requires": { "imurmurhash": "^0.1.4" } }, - "unique-stream": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", - "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", - "requires": { - "json-stable-stringify": "^1.0.0", - "through2-filter": "^2.0.0" - } - }, "unique-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", @@ -9736,7 +11398,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -9746,7 +11407,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -9757,7 +11417,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, "requires": { "isarray": "1.0.0" } @@ -9767,8 +11426,12 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" } } }, @@ -9779,10 +11442,9 @@ "dev": true }, "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==" }, "update-notifier": { "version": "2.5.0", @@ -9812,9 +11474,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -9822,16 +11484,10 @@ "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -9845,26 +11501,17 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "requires": { "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } } }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, "url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, "requires": { "punycode": "1.3.2", "querystring": "0.2.0" @@ -9873,8 +11520,7 @@ "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" } } }, @@ -9890,14 +11536,12 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "dev": true, + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "requires": { "inherits": "2.0.3" } @@ -9918,15 +11562,15 @@ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "v8-compile-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.0.tgz", - "integrity": "sha512-qNdTUMaCjPs4eEnM3W9H94R3sU70YCuT+/ST7nUf+id1bVOrdjrpUaeZLqPBPRph3hsgn4a4BvwpxhHZx+oSDg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "dev": true }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -9938,11 +11582,6 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, - "vendors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz", - "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==" - }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -9951,29 +11590,17 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } } }, "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==" }, "w3c-hr-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "dev": true, "requires": { "browser-process-hrtime": "^0.1.2" } @@ -9982,167 +11609,104 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", - "dev": true, "requires": { "chokidar": "^2.0.2", "graceful-fs": "^4.1.2", "neo-async": "^2.5.0" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "webpack": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.3.tgz", - "integrity": "sha512-3VcrVoFgzSz1IYgga71YpU3HO89Al5bSnDOj9RJQPsy+FNyI1sFsUyJITn3pktNuaRBlQT0usvKZE3GgkPGAIw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.5.13", - "@webassemblyjs/helper-module-context": "1.5.13", - "@webassemblyjs/wasm-edit": "1.5.13", - "@webassemblyjs/wasm-opt": "1.5.13", - "@webassemblyjs/wasm-parser": "1.5.13", - "acorn": "^5.6.2", - "acorn-dynamic-import": "^3.0.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.0.tgz", + "integrity": "sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==", + "requires": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/wasm-edit": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5", + "acorn": "^6.2.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", - "schema-utils": "^0.4.4", - "tapable": "^1.0.0", - "uglifyjs-webpack-plugin": "^1.2.4", - "watchpack": "^1.5.0", - "webpack-sources": "^1.0.1" - } - }, - "webpack-bundle-analyzer": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.13.1.tgz", - "integrity": "sha512-rwxyfecTAxoarCC9VlHlIpfQCmmJ/qWD5bpbjkof+7HrNhTNZIwZITxN6CdlYL2axGmwNUQ+tFgcSOiNXMf/sQ==", - "dev": true, - "requires": { - "acorn": "^5.3.0", - "bfj-node4": "^5.2.0", - "chalk": "^2.3.0", - "commander": "^2.13.0", - "ejs": "^2.5.7", - "express": "^4.16.2", - "filesize": "^3.5.11", - "gzip-size": "^4.1.0", - "lodash": "^4.17.4", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", "mkdirp": "^0.5.1", - "opener": "^1.4.3", - "ws": "^4.0.0" + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.1", + "watchpack": "^1.6.0", + "webpack-sources": "^1.4.1" }, "dependencies": { "acorn": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", - "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==" }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" }, - "ws": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", - "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", - "dev": true, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } } } }, - "webpack-command": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/webpack-command/-/webpack-command-0.4.1.tgz", - "integrity": "sha512-4u23PTUcjLJCcPqs53ugjFIp2c3I6rGZgYYwgD7LPPU745MzbS3tLXuOpKdgFNN+qDdWtzfLKZhMTQyf7jklcg==", - "dev": true, - "requires": { - "@webpack-contrib/config-loader": "^1.2.0", - "@webpack-contrib/schema-utils": "^1.0.0-beta.0", - "camelcase": "^5.0.0", - "chalk": "^2.3.2", - "debug": "^3.1.0", - "decamelize": "^2.0.0", - "enhanced-resolve": "^4.0.0", - "import-local": "^1.0.0", - "isobject": "^3.0.1", - "loader-utils": "^1.1.0", - "log-symbols": "^2.2.0", - "loud-rejection": "^1.6.0", - "meant": "^1.0.1", - "meow": "^5.0.0", - "merge-options": "^1.0.0", - "object.values": "^1.0.4", - "opn": "^5.3.0", - "ora": "^2.1.0", - "plur": "^3.0.0", - "pretty-bytes": "^5.0.0", - "strip-ansi": "^4.0.0", - "text-table": "^0.2.0", - "titleize": "^1.0.1", - "update-notifier": "^2.3.0", - "v8-compile-cache": "^2.0.0", - "webpack-log": "^1.1.2", - "wordwrap": "^1.0.0" + "webpack-bundle-analyzer": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.4.1.tgz", + "integrity": "sha512-Bs8D/1zF+17lhqj2OYmzi7HEVYqEVxu7lCO9Ff8BwajenOU0vAwEoV8e4ICCPNZAcqR1PCR/7o2SkW+cnCmF0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-walk": "^6.1.1", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.15", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "acorn": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", "dev": true }, "ansi-styles": { @@ -10154,16 +11718,10 @@ "color-convert": "^1.9.0" } }, - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "dev": true - }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -10171,196 +11729,362 @@ "supports-color": "^5.3.0" } }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "dev": true, - "requires": { - "xregexp": "4.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "has-flag": "^3.0.0" } }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "async-limiter": "~1.0.0" } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true } } }, - "webpack-log": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", - "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", - "dev": true, - "requires": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "loglevelnext": "^1.0.1", - "uuid": "^3.1.0" + "webpack-cli": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.9.tgz", + "integrity": "sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A==", + "requires": { + "chalk": "2.4.2", + "cross-spawn": "6.0.5", + "enhanced-resolve": "4.1.0", + "findup-sync": "3.0.0", + "global-modules": "2.0.0", + "import-local": "2.0.0", + "interpret": "1.2.0", + "loader-utils": "1.2.3", + "supports-color": "6.1.0", + "v8-compile-cache": "2.0.3", + "yargs": "13.2.4" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "has-flag": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "find-up": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "requires": { "has-flag": "^3.0.0" } + }, + "v8-compile-cache": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==" + }, + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" + } } } }, - "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "webpack-dev-middleware": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + } + }, + "webpack-hot-middleware": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.0.tgz", + "integrity": "sha512-xs5dPOrGPCzuRXNi8F6rwhawWvQQkeli5Ro48PRuQh8pYPCPmNnltP9itiUPT4xI8oW+y0m59lyyeQk54s5VgA==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "html-entities": "^1.2.0", + "querystring": "^0.2.0", + "strip-ansi": "^3.0.0" + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, - "websocket-stream": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.1.2.tgz", - "integrity": "sha512-lchLOk435iDWs0jNuL+hiU14i3ERSrMA0IKSiJh7z6X/i4XNsutBZrtqu2CPOZuA4G/zabiqVAos0vW+S7GEVw==", + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", "requires": { - "duplexify": "^3.5.1", - "inherits": "^2.0.1", - "readable-stream": "^2.3.3", - "safe-buffer": "^5.1.1", - "ws": "^3.2.0", - "xtend": "^4.0.0" + "iconv-lite": "0.4.24" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } } }, "whatwg-mimetype": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz", - "integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew==", - "dev": true + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=" + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } }, "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" } }, - "widest-line": { + "which-module": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", - "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "dev": true, "requires": { "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, "worker-farm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", - "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", - "dev": true, + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "requires": { "errno": "~0.1.7" } }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -10369,42 +12093,44 @@ } }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", + "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", "requires": { "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "safe-buffer": "~5.1.0" } }, + "x-xss-protection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", + "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" }, - "xregexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", - "dev": true - }, "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yallist": { "version": "2.1.2", @@ -10412,20 +12138,86 @@ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" }, "dependencies": { - "camelcase": { + "ansi-regex": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" } } }, diff --git a/package.json b/package.json index 517d351520b..32ee90d2a33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "Nightscout", - "version": "0.10.3-master-20180805", + "name": "nightscout", + "version": "13.0.1", "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": "AGPL-3.0", "author": "Nightscout Team", @@ -16,23 +16,30 @@ "type": "git", "url": "https://github.com/nightscout/cgm-remote-monitor.git" }, - "contributors": { - "name": "Nightscout Team", - "url": "https://github.com/nightscout/cgm-remote-monitor/graphs/contributors" - }, + "contributors": [ + { + "name": "Nightscout Team", + "url": "https://github.com/nightscout/cgm-remote-monitor/graphs/contributors" + } + ], "bugs": { "url": "https://github.com/nightscout/cgm-remote-monitor/issues" }, "scripts": { "start": "node server.js", - "test": "make test", + "test": "env-cmd ./my.test.env mocha --exit tests/*.test.js", + "test-ci": "env-cmd ./ci.test.env mocha --exit tests/*.test.js", "env": "env", "postinstall": "webpack --mode production --config webpack.config.js && npm run-script update-buster", "bundle": "webpack --mode production --config webpack.config.js && npm run-script update-buster", "bundle-dev": "webpack --mode development --config webpack.config.js && npm run-script update-buster", "bundle-analyzer": "webpack --mode development --config webpack.config.js --profile --json > stats.json && webpack-bundle-analyzer stats.json", - "update-buster": "node bin/generateCacheBuster.js >tmp/cacheBusterToken" + "update-buster": "node bin/generateCacheBuster.js >tmp/cacheBusterToken", + "coverage": "env-cmd ./test.env nyc mocha --exit tests/*.test.js", + "dev": "env-cmd ./my.env nodemon server.js 0.0.0.0", + "prod": "env-cmd ./my.prod.env node server.js 0.0.0.0" }, + "main": "server.js", "config": { "blanket": { "pattern": [ @@ -48,67 +55,85 @@ } }, "engines": { - "node": "8.11.x", - "npm": "6.x || 5.8.x || 5.7.x || 5.6.x" + "node": "^10.15.2 || ^8.15.1", + "npm": "^6.4.1" }, "dependencies": { - "ajv": "^6.5.1", + "@babel/core": "^7.5.5", + "@babel/preset-env": "^7.5.5", + "apn": "^2.2.0", "async": "^0.9.2", - "body-parser": "^1.18.3", + "babel-loader": "^8.0.6", + "base64url": "^3.0.1", + "body-parser": "^1.19.0", "bootevent": "0.0.1", - "compression": "^1.7.3", - "css-loader": "^0.28.11", + "braces": "^3.0.2", + "compression": "^1.7.4", + "css-loader": "^1.0.1", "cssmin": "^0.4.3", - "d3": "^3.5.17", - "ejs": "^2.6.1", - "errorhandler": "^1.5.0", - "event-stream": "^3.3.4", - "expand-braces": "^0.1.2", + "d3": "^5.12.0", + "ejs": "^2.6.2", + "errorhandler": "^1.5.1", + "event-stream": "3.3.4", "expose-loader": "^0.7.5", - "express": "^4.16.3", + "express": "^4.17.1", "express-minify": "^1.0.0", - "file-loader": "^1.1.11", - "flot": "^0.8.0-alpha", - "jquery": "^3.3.1", + "file-loader": "^3.0.1", + "flot": "^0.8.3", + "heapdump": "^0.3.15", + "helmet": "^3.20.0", + "jquery": "^3.4.1", "jquery-ui-bundle": "^1.12.1-migrate", "jquery.tooltips": "^1.0.0", - "js-storage": "^1.0.4", - "jsonwebtoken": "^8.3.0", - "lodash": "^4.17.10", - "long": "^3.2.0", - "mime": "^2.3.1", - "minimed-connect-to-nightscout": "^1.1.1", - "moment": "^2.22.2", - "moment-timezone": "^0.5.21", - "mongodb": "~3.0.11", + "js-storage": "^1.1.0", + "jsdom": "~11.11.0", + "jsonwebtoken": "^8.5.1", + "lodash": "^4.17.15", + "memory-cache": "^0.2.0", + "mime": "^2.4.4", + "minimed-connect-to-nightscout": "^1.3.2", + "moment": "^2.24.0", + "moment-locales-webpack-plugin": "^1.1.0", + "moment-timezone": "^0.5.26", + "moment-timezone-data-webpack-plugin": "^1.1.0", + "mongodb": "^3.3.0", "mongomock": "^0.1.2", - "mqtt": "^2.18.3", - "node-cache": "^4.2.0", + "node-cache": "^4.2.1", "parse-duration": "^0.1.1", - "prettyjson": "^1.2.1", - "pushover-notifications": "^0.2.4", + "pem": "^1.14.3", + "pushover-notifications": "^1.2.1", "random-token": "0.0.8", - "request": "^2.87.0", - "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", - "share2nightscout-bridge": "git://github.com/nightscout/share2nightscout-bridge.git#wip/generalize", - "shiro-trie": "^0.3.13", + "request": "^2.88.0", + "semver": "^6.3.0", + "share2nightscout-bridge": "^0.2.1", + "shiro-trie": "^0.4.8", "simple-statistics": "^0.7.0", - "socket.io": "^2.1.1", - "style-loader": "^0.20.2", + "socket.io": "~2.1.1", + "style-loader": "^0.23.1", + "swagger-ui-dist": "^3.23.5", + "swagger-ui-express": "^4.1.2", + "terser": "^3.17.0", "traverse": "^0.6.6", - "uglify-js": "^3.4.6", - "uuid": "^3.2.1" + "uuid": "^3.3.2", + "webpack": "^4.39.2", + "webpack-cli": "^3.3.7" }, "devDependencies": { + "babel-eslint": "^10.0.3", "benv": "^3.3.0", - "clear-require": "^2.0.0", + "env-cmd": "^8.0.2", + "eslint": "^6.2.1", + "eslint-loader": "^2.2.1", "istanbul": "^0.4.5", "mocha": "^5.2.0", - "moment-locales-webpack-plugin": "^1.0.7", - "should": "^13.2.2", - "supertest": "^3.1.0", - "webpack": "^4.16.3", - "webpack-bundle-analyzer": "^2.13.1", - "webpack-command": "^0.4.1" - } + "nodemon": "^1.19.1", + "nyc": "^14.1.1", + "should": "^13.2.3", + "supertest": "^3.4.2", + "terser-webpack-plugin": "^1.4.1", + "webpack-bundle-analyzer": "^3.4.1", + "webpack-dev-middleware": "^3.7.2", + "webpack-hot-middleware": "^2.25.0" + }, + "browserslist": "> 0.25%, not dead" } diff --git a/server.js b/server.js index 27d629034a2..df99724747a 100644 --- a/server.js +++ b/server.js @@ -54,12 +54,6 @@ require('./lib/server/bootevent')(env, language).boot(function booted (ctx) { return; } - if (env.MQTT_MONITOR) { - ctx.mqtt = require('./lib/server/mqtt')(env, ctx); - var es = require('event-stream'); - es.pipeline(ctx.mqtt.entries, ctx.entries.map( ), ctx.mqtt.every(ctx.entries)); - } - /////////////////////////////////////////////////// // setup socket io for data and message transmission /////////////////////////////////////////////////// @@ -71,9 +65,6 @@ require('./lib/server/bootevent')(env, language).boot(function booted (ctx) { ctx.bus.on('notification', function(notify) { websocket.emitNotification(notify); - if (ctx.mqtt) { - ctx.mqtt.emitNotification(notify); - } }); //after startup if there are no alarms send all clear diff --git a/setup.sh b/setup.sh index 605696532ff..d77c8f3d1de 100755 --- a/setup.sh +++ b/setup.sh @@ -2,8 +2,6 @@ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - sudo apt-get update -sudo apt-get install -y nodejs -sudo apt-get install -y python-software-properties python git -sudo apt-get install -y build-essential +sudo apt-get install -y nodejs build-essential git npm install \ No newline at end of file diff --git a/static/colorbrewer/README.md b/static/colorbrewer/README.md index 9f3c8f79c07..fed63690d50 100644 --- a/static/colorbrewer/README.md +++ b/static/colorbrewer/README.md @@ -1,3 +1,11 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [colorbrewer](#colorbrewer) + + + colorbrewer =========== diff --git a/static/css/drawer.css b/static/css/drawer.css index 41afd6fd400..f9e6147fe2d 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -227,6 +227,7 @@ h1, legend, overflow: hidden; } #buttonbar { + margin-right: 50px; padding-right: 15px; height: 44px; opacity: 0.75; diff --git a/static/css/main.css b/static/css/main.css index 75492290675..7c2e25837cb 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -86,6 +86,18 @@ body { z-index: 2; } +button { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.ui-button { + background-color: grey; + border: 1px solid white; + padding: 2px; +} + a, a:visited, a:link { color: #2196f3; text-decoration: none; @@ -107,12 +119,13 @@ a, a:visited, a:link { font-family: 'Ubuntu', Verdana, Helvetica, Arial, sans-serif; vertical-align: middle; clear: both; + white-space: nowrap; } .bgStatus { float: right; text-align: center; - white-space: nowrap; + white-space: normal; padding-right: 20px; } @@ -128,6 +141,10 @@ a, a:visited, a:link { color: #4cff00; } +.bgStatus .bgSpan { + white-space: nowrap; +} + .bgStatus .currentBG { font-size: 120px; line-height: 100px; @@ -228,14 +245,16 @@ a, a:visited, a:link { .status { color: #808080; font-size: 100px; + white-space: normal; } .statusBox { text-align: center; - width: 250px; + width: 40%; } .statusPills { font-size: 20px; line-height: 23px; + margin-left: 5px; } .loading .statusPills { @@ -385,7 +404,7 @@ a, a:visited, a:link { margin: 4px; padding: 0; color: #808080; - width: 250px; + width: 40%; text-align: center; } @@ -417,9 +436,9 @@ a, a:visited, a:link { font-size: 12px; } -@media (max-width: 800px) { +@media (max-width: 1000px) { .bgStatus { - width: 300px; + width: 450px; } .bgButton { @@ -462,6 +481,7 @@ a, a:visited, a:link { .focus-range { margin: 0; + width: 50%; } .focus-range li { @@ -479,7 +499,7 @@ a, a:visited, a:link { @media (max-width: 750px) { .bgStatus { - width: 240px; + width: 50%; padding: 0 0 20px 0; } @@ -491,6 +511,10 @@ a, a:visited, a:link { font-size: 12px; } + .statusBox { + width: auto; + } + .bgStatus .currentBG { font-size: 70px; line-height: 60px; @@ -729,10 +753,6 @@ a, a:visited, a:link { display: block; } - .statusBox { - width: 220px; - } - .statusPills { display: inline; margin-left: auto; diff --git a/static/css/report.css b/static/css/report.css index 9ec304cb1a7..afe046f00d9 100644 --- a/static/css/report.css +++ b/static/css/report.css @@ -25,7 +25,7 @@ body { #tabnav { text-align: left; margin: 1em 0 1em 0; - font: bold 11px verdana, arial, sans-serif; + font: bold 11pt verdana, arial, sans-serif; border-bottom: 1px solid #6c6; list-style-type: none; padding: 3px 10px 3px 10px; @@ -59,7 +59,7 @@ body { text-align: left; padding: 4px; font-size: 14px; - line-height: 15px; + line-height: 15pt; background: white; border: 2px solid black; border-radius: 8px; diff --git a/static/glyphs/css/fontello-embedded.css b/static/glyphs/css/fontello-embedded.css index 1bd83157f9a..92ad1b9d889 100755 --- a/static/glyphs/css/fontello-embedded.css +++ b/static/glyphs/css/fontello-embedded.css @@ -1,15 +1,15 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?37278242'); - src: url('../font/fontello.eot?37278242#iefix') format('embedded-opentype'), - url('../font/fontello.svg?37278242#fontello') format('svg'); + src: url('../font/fontello.eot?73205958'); + src: url('../font/fontello.eot?73205958#iefix') format('embedded-opentype'), + url('../font/fontello.svg?73205958#fontello') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'fontello'; - src: url('data:application/octet-stream;base64,d09GRgABAAAAABmIAA8AAAAAKsgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIwleU9TLzIAAAGUAAAAQwAAAFY+IUkiY21hcAAAAdgAAACmAAACiOnFAFpjdnQgAAACgAAAABMAAAAgBtX/AmZwZ20AAAKUAAAFkAAAC3CKkZBZZ2FzcAAACCQAAAAIAAAACAAAABBnbHlmAAAILAAADcEAABWEVKl1aWhlYWQAABXwAAAAMgAAADYLJgpEaGhlYQAAFiQAAAAgAAAAJAfIA/ZobXR4AAAWRAAAADoAAABYTOP/+2xvY2EAABaAAAAALgAAAC47fjW8bWF4cAAAFrAAAAAgAAAAIAFdDDxuYW1lAAAW0AAAAXcAAALNzJ0cHnBvc3QAABhIAAAAxAAAAR260xEFcHJlcAAAGQwAAAB6AAAAhuVBK7x4nGNgZGBg4GIwYLBjYMpJLMlj4HNx8wlhkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAKVkFSAB4nGNgZK5nnMDAysDAVMW0h4GBoQdCMz5gMGRkAooysDIzYAUBaa4pDA4vGF6IMAf9z2KIYg5imA4UZgTJAQDp2AvBAHic7ZLbDQIxDATnuPA+3lwXVEJBfFHwdgHrsGUQaSzFcqJoJ8ASGM3DNBjeDNR6uTv0/siu9xvPPtOqr/vn40pV71uvC88237hizYatz+2ZOHDkxJkLV27cmT264r+mKsOU3Vxp/uj5BmeKQnlSqMQVyqGCs0fBFlCwDxRsBoVyq2BbKNTrFGwQBbtEwVZRsF8UbBoFO0fB9lHwP/Af+sH8BeUZMdoAAHicY2BAAxIQyBz0PxOEARJmA9sAeJytVml300YUHXlJnIQsJQstamHExGmwRiZswYAJQbJjIF2crZWgixQ76b7xid/gX/Nk2nPoN35a7xsvJJC053Cak6N3583VzNtlElqS2AvrkZSbL8XU1iaN7DwJ6YZNy1F8KDt7IWWKyd8FURCtltq3HYdERCJQta6wRBD7HlmaZHzoUUbLtqRXTcotPekuW+NBvVXffho6yrE7oaRmM3RoPbIlVRhVokimPVLSpmWo+itJK7y/wsxXzVDCiE4iabwZxtBI3htntMpoNbbjKIpsstwoUiSa4UEUeZTVEufkigkMygfNkPLKpxHlw/yIrNijnFawS7bT/L4vead3OT+xX29RtuRAH8iO7ODsdCVfhFtbYdy0k+0oVBF213dCbNnsVP9mj/KaRgO3KzK90IxgqXyFECs/ocz+IVktnE/5kkejWrKRE0HrZU7sSz6B1uOIKXHNGFnQ3dEJEdT9kjMM9pg+Hvzx3imWCxMCeBzLekclnAgTKWFzNEnaMHJgJWWLKqn1rpg45XVaxFvCfu3a0ZfOaONQd2I8Ww8dWzlRyfFoUqeZTJ3aSc2jKQ2ilHQmeMyvAyg/oklebWM1iZVH0zhmxoREIgIt3EtTQSw7saQpBM2jGb25G6a5di1apMkD9dyj9/TmVri501PaDvSzRn9Wp2I62AvT6WnkL/Fp2uUiRen66Rl+TOJB1gIykS02w5SDB2/9DtLL15YchdcG2O7t8yuofdZE8KQB+xvQHk/VKQlMhZhViFZAYq1rWZbJ1awWqcjUd0OaVr6s0wSKchwXx76Mcf1fMzOWmBK+34nTsyMuPXPtSwjTHHybdT2a16nFcgFxZnlOp1mW7+s0x/IDneZZntfpCEtbp6MsP9RpgeVHOh1jeUELmnTfwZCLMOQCDpAwhKUDQ1hegiEsFQxhuQhDWBZhCMslGMLyYxjCchmGsLysZdXUU0nj2plYBmxCYGKOHrnMReVqKrlUQrtoVGpDnhJulVQUz6p/ZaBePPKGObAWSJfIml8xzpWPRuX41hUtbxo7V8Cx6m8fjvY58VLWi4U/Bf/V1lQlvWLNw5Or8BuGnmwnqjapeHRNl89VPbr+X1RUWAv0G0iFWCjKsmxwZyKEjzqdhmqglUPMbMw8tOt1y5qfw/03MUIWUP34NxQaC9yDTllJWe3grNXX27LcO4NyOBMsSTE38/pW+CIjs9J+kVnKno98HnAFjEpl2GoDrRW82ScxD5neJM8EcVtRNkja2M4EiQ0c84B5850EJmHqqg3kTuGGDfgFYW7BeSdconqjLIfuRezzKKT8W6fiRPaoaIzAs9kbYa/vQspvcQwkNPmlfgxUFaGpGDUV0DRSbqgGX8bZum1Cxg70Iyp2w7Ks4sPHFveVkm0ZhHykiNWjo5/WXqJOqtx+ZhSX752+BcEgNTF/e990cZDKu1rJMkdtA1O3GpVT15pD41WH6uZR9b3j7BM5a5puuiceel/TqtvBxVwssPZtDtJSJhfU9WGFDaLLxaVQ6mU0Se+4BxgWGNDvUIqN/6v62HyeK1WF0XEk307Ut9HnYAz8D9h/R/UD0Pdj6HINLs/3mhOfbvThbJmuohfrp+g3MGutuVm6BtzQdAPiIUetjrjKDXynBnF6pLkc6SHgY90V4gHAJoDF4BPdtYzmUwCj+Yw5PsDnzGHQZA6DLeYw2GbOGsAOcxjsMofBHnMYfMGcdYAvmcMgZA6DiDkMnjAnAHjKHAZfMYfB18xh8A1z7gN8yxwGMXMYJMxhsK/p1jDMLV7QXaC2QVWgA1NPWNzD4lBTZcj+jheG/b1BzP7BIKb+qOn2kPoTLwz1Z4OY+otBTP1V050h9TdeGOrvBjH1D4OY+ky/GMtlBr+MfJcKB5RdbD7n74n3D9vFQLkAAQAB//8AD3icrVh7bFvXeT/fueeee3lJXr7uQyKpK4qUSJmiKIVPx6JpSpathxknsWmFVhWFdiQ5taw4wOJHkyBovQyBByz2gqBLhbWxgyR/BJnXdH90K9A0aFqkGArEAYo+5mB/DUOKDenQbcay2fS+e0knbubNSFY9zj33u+ec+72/33cJu3HjxlnhdSFPAmSA7CR1cv67+WRCEBnM7Pm2cl+zViJcYAJnG4SJAhPXiSRTKq0SQgROhBYRiSyJ8iFCAWiDUAoHCVC4J1Ir32ajLNGNO+08WPOEQmNR3ewxB11i30igUKVmzqKqEM/SsmbRqlAuZCGuQtks5XSN+wD/VOiDfLFQKldhB9hjuZBMxAXNyJWylM8//cahhy49VQecvI3Xk1uXz5z/aqsEyy+/9crSL/pHDzHgIt1qeE5wATQp5OEhDRq1/YH0zjRN7h6G5Zvbf3Dp6fn5py89NPPM0jZaan117qGXl5dfvn+1z2QyoNr6POGeobDAKcjMLfHAcOKu6fafxOLxWhwGCUXpCW3Sh4lORshIbbgPqCiBQOkMEal4nFCBHmcgEOE4ACFrptZjahoXwyNDGsoZHwPJHpKFMpRyJhjF21Jpk0dFlX9wRfSzKOfA8AYYvy3xpIRk8coVUYxymyCK7Wv8PYf4wQc3iZwj0WG9y/+Jm/wPgQA+IAKdEYEBO47GFOA4sQXAlWua2aOZDv+oVeQ0hUyifXL9ULYH5P92VOS/fQ3ZxJc6PF/5oMPz7YjwmGQzZzOpfsr0ezZRFDtEzq9cwT22AAIhXX/3kB4ySKZqO2RgqHomtiSg+JwKLQ4Eld/AC4EmwZt6uFf1Aon19Q6GE1rA26Oaskg84HaJ2shQzkAnTAzEk4WSCTlD4ylnXnbmSM+iPwpT6Wolcz2ZqVTT9O8ylc68krHn1fT15EgFKpnFymi6Wl2oAhyrNqpQWagAbFQPIKkCHd5x+Hv6Z8h7nOTIdG0Stc0J8rkhyJRInEjrGFUcRN4iEmNSg0gSaxImsTqQu7JbkgNWr6GFVC8u9YDH5r4cT26HQiln9IHGkWMTZUl1REgFCqX8QM4QAoUk3nMzoBl4X3pfj8XSlkXPZSpQHVmqXX9nchGWanT75OIRy7j+jh4Dy6Db9dgPLeN93bJ03AB3V0YeyVRhcgkWp2Dr5NJS+1nYalgQ09s/wTW2aOwW+XrJGNlB7q3VmYCBRGQUUXRThRGXTFyYSpAiyS3i4tzVIC4XbxLu4nXbSNWJUj47kkwMxCJhLeTtVXs7wnpsU8WT5aItrCPl55ccNmzZMlGw3vwiOvgc6sA4u/Fl4UPhfpIi42S+NjM0mIhHwr09MgVdA4Jq8ftUiYtMIoIwE8Cg242JVAAqbKCjYAJZRx/2zKJGvWRuLDuS7rfCTNRHQJe4ZEopTJmJVDIlpcrJMSiWS+UUZtMdkDcNs2xKhikVkphhMXsKH/6N4j699pLWb52L6MbF1a+45XfflTynVy/4YtFzUSP8rbVTXunlhdMN2nx8EV59Lhwxv7lyUnH/8MeS+9TKBb03+lxU73lp5ZTseecdyXNi5c8j+sOVRuN0o4HeeqvNk6SCFWix9gAXBQEUoqDZJZV5qYcTt0Lc68SFNJfSIm5ZdjeI2y03ieyW65Gwbfo9sztrE1vz49nR4dRALJyMJG91AN//6QCfPPl/eAMkHAJOFr6gW3yugOGf0V2ezJGDZLV2GMuqwMCDQpMNV4D7mY+qMvF6iHeduO205WkRr6J4G8TrVZpE8Sr14VRHhc0D990zu6u2fWJbsZAdTeWH8/9TkcE7KTL1iSJTvw+tvm1LjZPv/B61+3kUbcfi94RZgRKZaGS4NqSFggGMPq/HrbixOsxQHMhxjLc1ICLD1CQLqCIU189jSX8JyhhmGHMYWGV45dLVq+1Xrl4Ftrmw+WJzc7P54uaCQDu0pavtpc2FF1/EJ/bYrVcbmAfuxUhRiUHStZQL7HeGgorAyC5DZQJME/v9WAtgDbeIRAwIGOnFQB4CMFQqpMqGqUlmyY8Tv/TPcLj9LbgwNzfzZbo+9uST9c3N5+ACDFyzYCAx/9P6/NEX/nR17BS0npzfbNc3iX02uXEUediHzpOtjdjSCljxNxAwoZsJrJtpGPOyOVzuJu6A/SOJvcjDwCe/cATCcKT9jfY/vn9z0v4GHOnIeJSO4/kRMkwOk121qWKOUqEEjDbvoQgjERsxzkS+gT7POKY2ipiSshamPrtAf5Ln9u8LRwZTfWHJwRsqaBbYjue4ZxUQNHbcVBclbhr5XLmUQowYQnyJ4KNYRhxiav2CkTQSmkp9lCd5kfdBlRYLCDMtMIuFMYrZMgt0vHX4cOuNX75hXy797FLbCjJ56alnmqrVOnr6vBz19PYt0ZV/W2GLo6FAwGN5qMJlj89H44vy49OFqSf40kjQK3okRh/NPLJcWVrLnozv3Rs/mV1bqiw/kjkZ27s3xlWZF3KKVmrPB61BtSe6ZOVKpVxscSSk63RcCQZVxef30zgsxqZYcjxuWfHxJJuylraEVFnzGI5SbNtdRN3KiPDDJEEypEiqZIbsJ0vkGDlB/oh8rfb0g1hUNho7RplbKQ0GEf2jynUjqAqKW1daZsgnuP0eGVG8m7W8LipySgmIpEU0rbdJens9swEJbQE9TdLT4+2Ze+ZrT33l8T947Pj6Iw+3vrT4wMK+++t7du+amtxeuXtrIT8+NpLWQp3fYBghvonFhlvggHa76lQh5VBoSbyVoIIFpd990iU4E8CUBVyyTVUSbhIx/SAWiKMdO+lF6m4PBTqYrdhNQz9Pb8vQ4WryV+nKMM1szf6qew9/2SX88k4P4PX0tjRktmZ+0SX8TPNfL6mGodK/9RnQ3TafiV77qC+d7hOCfWkJJza5v3v9dyvt7P3eZ+5/e5P+mfv2Yz/vS9NsxBnh3S71vXP2S+3h1S4Fzlx7zXnnIo5OPvsPOiGMYT4ZI4O1gS5ad3IYtmENjCjaRDRB61Et3MvEnpGhQipuO34qGZcwNIDrdmM1hCGWTBXKiBvQdFVUt2kJpgGWG2QVyw7VgKHbuNwoDERf++g1GGX9W3urPjFqRZm/Ol4TRt/TQh7dFfO4XF5Lc3tCGnK4euo1+vqJ+2fFbCP5RMGvUX+w8ER9SZzp5OI32SnBjWmun+ypzfa4EbqbQb8gsACCH/RbELH/EBn2Ukxg2IlQ4ThHZE/oPrxQsmAfMk9IX0TXVK+TLTFVGSOiPhDIBzAO+Bh0WqqU05GgzKUJzOXs1H+dnRE+1JTp9kvYVJz5cB1msAs5A8JOl+z9V0mRZoXFax/Rv/6tR3HZa/iZXx+D3dxeMS0bCua5G9cQ5M7QoxiJY2SRrNUeZqBQmCGC4lIE1waRXKJLEjc8wNyAYJe2iEJckuJqYWeJib2BF5E0uQpebFlIXQsCeeDA3nvmZqendlS33V0sZNLJoVh/JBwc08ZIAAK+DsIvBySOXQhmOQPLLN8C8STO7UjQ7exYRBNir+KDfM5QAbCdQbuW7Uwp2WYuJCWnn8lV0dpof/SDcilvP/wmls8+LR1UtSPRcYjB2+1r2GG8rw1vS1vpWCH3/Gr+9LN6jEbh2fLKH69BbDgOyVIyGcHKrvsNGlZ7FSU96HenFyYacDCmQ92nqMNpOa/Gw+2WHqvhYRPDqX7DsozZfG71fP40HgxD+bndh+natH1MJJksDduAgRpxjWLOMoZDexe2Ze1CSG58LPwD+gonPnI3Zr1ZkGp6rTrhx6ZCIqyAlZTOTGLfurvzrWOcSAITJLaBdsJnqxgYjAgM1Q+Ei8AfwkaRiohaRTs+RFqP7Pm2G/dt6a7HRv7OG8wv9KLaXbduYQQ27rjn4MGDNawB0zu3V+zez4oYIdQE11wIDoYQk+h5HZIYywb6Qkj/NB2WB3KmhU09TzmZU0JgZmLmxNppO5BgQqKIwdHp1DX4l9qBWhF0l+tHriD+Dy7vbI/vXF7eCZcTlkuQIrLi9bTHhwpQGoTLQwVxUO4pX2yfvUiP5y/m/Rn/Af/3Jw9M9pfg+ZtHtN862jlgahlUFuJRmQmFoe4ZuyU8QYZzF9pnL0C2cLHg8x3wZ5y88DH9Q7S1hHkBUZKIcA0QqtANQumjtpZZAyGKDRcY1BOhoVLIb3+WCA1ggVDBFDEupC4sNfMOZkUF5Yy/gr7GyQbA5Zhx/deolpgReOGnX6dBnL56bKJB79t+sf2WgxZhyojBsbUXXlg7ZjmY6WOsux4yRMZro8xmBZMQGs+GTusIXB4lIoBjLpsnEeqhoWJCH3Q+laAxpISDWpA1hw3zVvaQ5SoYwkVDHVSNfaf3QdFhrMsfnHr+8nkaOOfTAKuAw+Ixy/wdJo88T7/u9A833hSCqDMf6myMTJCd5AhZqR16YBfl8paB3oALOHI9g7rsfmZg9OZnBhU7cdXV8nmp7FEoB5kvE8ntdj45uJvELbnrqyuHlr90cP++e+tzM5M7tEEtaf8k/FjwAcUZgY672cnVvMN9CPEjlnUs1FWAPPatCS6Jur2mW8ZTgY6+cLX97Q811m9nM7AUeVBWnOHcp9PnFKkzlZTZtkfGnvoyleX22f+MMPFNzuCfFLnU9biive6NlCtjfMdMu1J/ISvw3fYPbCJM2uP/Mm+v0MD133g0RdHo6qRt6f34xuu/yU5PZWnIYeJBHXsc7UHlvwFt9oFbAAAAeJxjYGRgYABin/13C+L5bb4ycDO/AIowXD75OAhG///zP5OlnBnE52BgAokCAJl3Ds8AAHicY2BkYGAO+p/FwMBS9v/P//8s5QxAERQgBgCjFAbFeJxjfsHAwLzy/3+mJgYGEGZeBcT3oPgFEtsDygdiJgMobc3AwFIGxO7//4D5IP0LgFiQgQEAfdISGwAAAAAAAAC4ARgBeAHWAkYCzANAA94EkgTQBRIFQgXwBvoHWAe2CG4JZAmsCf4KwgAAAAEAAAAWAIYADQAAAAAAAgA0AEQAcwAAAJILcAAAAAB4nHWQy07CQBSG/5GLCokaTdw6KwMxlksiCxISEgxsdEMMW1NKaUtKh0wHEl7Dd/BhfAmfxZ92MAZim+l855szZ04HwDW+IZA/Txw5C5wxyvkEp+hZLtA/Wy6SXyyXUMWb5TL9u+UKHhBYruIGH6wgiueMFvi0LHAlLi2f4ELcWS7QP1ouknuWS7gVr5bL9J7lCiYitVzFvfgaqNVWR0FoZG1Ql+1mqyOnW6moosSNpbs2odKp7Mu5Sowfx8rx1HLPYz9Yx67eh/t54us0UolsOc29GvmJr13jz3bV003QNmYu51ot5dBmyJVWC98zTmjMqtto/D0PAyissIVGxKsKYSBRo61zbqOJFjqkKTMkM/OsCAlcxDQu1twRZisp4z7HnFFC6zMjJjvw+F0e+TEp4P6YVfTR6mE8Ie3OiDIv2ZfD7g6zRqQky3QzO/vtPcWGp7VpDXftutRZVxLDgxqS97FbW9B49E52K4a2iwbff/7vB+NphE8AeJxtjetKAzEUhDNtrO7FXrzUpwiswuLzpGcPu8FsEnJR+vauSAuC82PmGxgYsRK/qsX/OmKFNSRusMEt7lChRoMW99hihz0OeMAjnvCMI15EO7ENikwky8NOu9GyGnw5LVHC4U8f/JeTM7tSn3TOHM/qrb9i363Jj9f63jcXfO26LWlHbC83m09vy8wy2JKqyZc4Wp2SJG1JZuNyTZOOWVnjuE1+IbesoyHJg8nSevqofkz5wK7JUadJ8RzyWYhvVYpJbnicY/DewXAiKGIjI2Nf5AbGnRwMHAzJBRsZWJ02MTAyaIEYm7mYGDkgLD4GMIvNaRfTAaA0J5DN7rSLwQHCZmZw2ajC2BEYscGhI2Ijc4rLRjUQbxdHAwMji0NHckgESEkkEGzmYWLk0drB+L91A0vvRiYGFwAMdiP0AAA=') format('woff'), - url('data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCMJXkAAAD8AAAAVE9TLzI+IUkiAAABUAAAAFZjbWFw6cUAWgAAAagAAAKIY3Z0IAbV/wIAAB6wAAAAIGZwZ22KkZBZAAAe0AAAC3BnYXNwAAAAEAAAHqgAAAAIZ2x5ZlSpdWkAAAQwAAAVhGhlYWQLJgpEAAAZtAAAADZoaGVhB8gD9gAAGewAAAAkaG10eEzj//sAABoQAAAAWGxvY2E7fjW8AAAaaAAAAC5tYXhwAV0MPAAAGpgAAAAgbmFtZcydHB4AABq4AAACzXBvc3S60xEFAAAdiAAAAR1wcmVw5UErvAAAKkAAAACGAAEAAAAKADAAPgACbGF0bgAOREZMVAAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDfwGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgA6BQDUv9qAFoDUgCXAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAF8AAEAAAAAAHYAAwABAAAALAADAAoAAAF8AAQASgAAAAQABAABAADoFP//AADoAP//AAAAAQAEAAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAQwAAAAAAAAAFQAA6AAAAOgAAAAAAQAA6AEAAOgBAAAAAgAA6AIAAOgCAAAAAwAA6AMAAOgDAAAABAAA6AQAAOgEAAAABQAA6AUAAOgFAAAABgAA6AYAAOgGAAAABwAA6AcAAOgHAAAACAAA6AgAAOgIAAAACQAA6AkAAOgJAAAACgAA6AoAAOgKAAAACwAA6AsAAOgLAAAADAAA6AwAAOgMAAAADQAA6A0AAOgNAAAADgAA6A4AAOgOAAAADwAA6A8AAOgPAAAAEAAA6BAAAOgQAAAAEQAA6BEAAOgRAAAAEgAA6BIAAOgSAAAAEwAA6BMAAOgTAAAAFAAA6BQAAOgUAAAAFQAE////iQOqAzMAEQAhAEMATACQtzMmIwMFBAFHS7AKUFhANgAGAwQDBgRtAAQFAwQFawAHCAICB2UAAAADBgADYAAFAAgHBQhhAAIBAQJUAAICAVkAAQIBTRtANwAGAwQDBgRtAAQFAwQFawAHCAIIBwJtAAAAAwYAA2AABQAIBwUIYQACAQECVAACAgFZAAECAU1ZQAwTEy8cFRcYFyQJBR0rETQ+AhcyHgIOAyIuAjcUHgI+Azc0LgEiDgE3FzYyFRQGDwEGDwEOAR0BMzU0Njc+AT8BNjc+ATc0JiMiAxQWMjYuAgZKfqxhX658TAFKfqzArnxMdjhegpCAYDYBXqK+pFzXHy1hBAEGBQI4Fgx1BgMBFAcTDAYTFAFUQFMRKkMqAiZGKAFeX658TAFKfqy/rn5KSn6uX0eEXDoCNmCASV+iXl6iUWUdFwQIAQUEAR0MGhglGgMGAgEIBAsHBhEoIzFE/o0gIiJAIgEkAAIAAAAAAlgCYwAVACsAK0AoHQECBQcBAwICRwAFAgVvAAIDAm8EAQMAA28BAQAAZhcUGBcUFAYFGislFA8BBiIvAQcGIi8BJjQ3ATYyFwEWNRQPAQYiLwEHBiIvASY0NwE2MhcBFgJYBhwFDgbc2wUQBBwGBgEEBQ4GAQQGBhwFDgbc2wUQBBwGBgEEBQ4GAQQGdgcGHAUF29sFBRwGDgYBBAUF/vwGzwcGHAUF3NwFBRwGDgYBBAYG/vwGAAAAAAIAAAAAAlgCdQAVACsAK0AoJQEDAQ8BAAMCRwUBBAEEbwIBAQMBbwADAANvAAAAZhQXGBQXFAYFGisBFAcBBiInASY0PwE2Mh8BNzYyHwEWNRQHAQYiJwEmND8BNjIfATc2Mh8BFgJYBv78BRAE/vwGBhwFDgbb3AUQBBwGBv78BRAE/vwGBhwFDgbb3AUQBBwGAXAHBv78BgYBBAYOBhwFBdzcBQUcBs8HBv78BQUBBAYOBhwGBtvbBgYcBgAAAAMAAP+JA6oDMwAMABgAJABCQD8IAQQABQIEBWAHAQIAAwACA2AGAQABAQBUBgEAAAFYAAEAAUwaGQ4NAQAgHRkkGiMUEQ0YDhcIBQAMAQsJBRQrJTIWFRQGIyEiJjQ2FwEyFhQGJyEiJjQ2NwEyFhQGIyEiLgE2NwNCKj48LP0mLDw+KgLaLDw8LP0mLDw8LALaLDw+Kv0mKzwBPCxaPC0qPj5WPgEBbD5UPgE8VjwBAW0+VT4+VjwBAAAAAwAAAAAD3gKXAAwAIgAyAERAQQIBAQYABgEAbQMIAgAHBgAHawAFAAYBBQZgAAcEBAdUAAcHBFgABAcETAEAMS4pJiEeGRYUEw4NBwYADAEMCQUUKzciJj0BNDYyFh0BFAYBMhYXFRQGJxQGJyEiJicRNDYzITIWAxE0JichIgYXERQWMyEyNtEVICAqHh4Cjyw8AT4rXED9w0FaAVxAAj1BWmceFv3DFSABHhYCPRUgwh4W0RUeHhXRFSABOTwraCw+AUFcAVpCAThBXFz+hwE4Fh4BIBX+yBUeHgAAAAAEAAAAAAPeApcADAAZAC8APwBPQEwEAwIBCAAIAQBtBQsCCgQACQgACWsABwAIAQcIYAAJBgYJVAAJCQZYAAYJBkwODQEAPjs2My4rJiMhIBsaFBMNGQ4ZBwYADAEMDAUUKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjYBbRUgAR4sHAEesRUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAgAA/2kD6ANRACcAMABKQEclJCMiGxoZGAgCARUUAQAEAwIQDw4HBgUEBwADA0cRAQMBRgACAQMBAgNtAAMAAQMAawABAQxIAAAADQBJLy4rKh8eGgQFFSsBFQcGBxcHJwYPASMnJicHJzcmLwE1NzY3JzcXNj8BMxcWFzcXBxYXBzQmIg4BFjI2A+i5Cgt4Zp8UHx6PGxUWoWV5CwjHxwcMeGWgDyAcjxwWGp5mdw0HolZ4VAJYdFoBpY4aGxedZHYKC8LFBwt3ZKAVGRyOHBUYn2R3CAzDwwcMdWScGxVjPFRUeFRUAAUAAAAAA94ClwAMABkAJgA8AEwAWkBXBgUDAwEKAAoBAG0HDgQNAgwGAAsKAAtrAAkACgEJCmAACwgIC1QACwsIWAAICwhMGxoODQEAS0hDQDs4MzAuLSgnISAaJhsmFBMNGQ4ZBwYADAEMDwUUKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGJSImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BIxUgAR4sHh4BViw8AT4rXED9w0FaAVxAAj1BWmceFv3DFSABHhYCPRUgwh4W0RUeHhXRFSABHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAAGAAAAAAPeApcADAAZACYAMwBJAFkAZUBiCAcFAwQBDAAMAQBtCREGEAQPAg4IAA0MAA1rAAsADAELDGAADQoKDVQADQ0KWAAKDQpMKCcbGg4NAQBYVVBNSEVAPTs6NTQuLSczKDMhIBomGyYUEw0ZDhkHBgAMAQwSBRQrJSImNzU0NjIWFxUUBiciJj0BNDYyFh0BFAYlIiYnNTQ2MhYdARQGJyImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BwBYeASAqHh6yFSABHiweHgFWLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAACAAD/ugNIAwIACAAUAChAJRQTEhEQDw4NDAsKCwEAAUcCAQABAG8AAQFmAQAFBAAIAQgDBRQrATIWEAYgJhA2ATcnBycHFwcXNxc3AaSu9vb+pPb2AQSaVpqYWJqaWJiaVgMC9v6k9vYBXPb+XJpWmJhWmphWmJhWAAAAAwAA/20D6ANPAAUADgAWACpAJwkBAQABRxMSCgMEAEUWDgQDAUQAAAEAbwIBAQFmAAAABQAFEQMFFSs1ETMBEQElNjQnNxYXFAcXNhAnNxYQB+wBYv6eAaBJSUdpAmsve3tMmpqOAaABIfweASEjSsxMSmqUkWUvdwFge0qa/kyaAAAAAQAA/2oD6ANSAAsALkArAgEAAQMBAANtBgUCAwQBAwRrAAEBDEgABAQNBEkAAAALAAsREREREQcFGSs1ESERIREhESERIREBZwEaAWf+mf7m0QEaAWf+mf7m/pkBZwAAAwAA/2oCMANSABsAKABiAEVAQjUyAgIDNgEEAlhNAgAGA0cABQQGBAUGbQAGAAQGAGsAAgAEBQIEYAADAwFYAAEBDEgAAAANAElTUhobJCcdGgcFGisBFA4BFB4BHQEUBiImPQE0PgE0LgE9ATQ2MhYVBQcGFxYzMjc2JyYjIhM0PgI/ATY1NwYiJxcUHwMWJhYjFA4CDwIGJgY1Bh0BPgI1NDIVFB4BFzU0LwImLwEuAQIwYGJiYKzYrGBiYmCu1K7+HhIECFx8hFgOHmBqeJAIHAwZHVwCZPRkBFotExERDB4MAgoGCAwPDwIiWgh0RDRCegZcKxINBQwHBAJuLGhePFxmLnYiTk4idi5mXDxeaCx2IE5OIAYOCAY0MgoUNv5KEh4kDhgcXB4yNjYyIForExUVAjAKEhIOCg8QEAIiAVogQgQmMCIeHiIwJgRCHlwpEw4IFAwWAAAADQAA/2oDoQNSAAgAEQAaACMALAA1AD4ARwBTAFwAbAB1AIUAgUB+XQEVFG1UPy0ECwo2JBIDBQQDRwAVFhIOAwoLFQpgFxMPAwsQDAgDBAULBGANCQIFBgICAAEFAGAAFBQZWAAZGQxIEQcDAwEBGFgAGBgNGEmEgXx5dHNwb2toY2BbWldWUlFMS0ZFQkE9PDk4NDMwLysqFBMUExQTFBMSGgUdKxc0JiIGHgE+ATc0JiIOARY+ASc0JiIGHgI2BTQmIg4BFj4BJzQmIg4BHgE2JzQmIgYeAjYFNCYiDgEeATYnNCYiDgEeATYBNTQuAQYHFRQeATYDNCYiDgEeATY3NTQmIyEiBh0BFBYzITI2BzQmIgYeAjYTERQGIyEiJjURNDYzITIW1io6LAIoPibZKjwoAiw4LtkqOiwCKD4mAa8qPCgCLDgu2Co8KAIsOC7ZKjosAig+JgGvKjwoAiw4LtgqPCgCLDguAaoqOioBLDgs1yo8KAIsOC7UFBD9Ng4WFg4Cyg8WASo6LAIoPiZKLBz87h0qKh0DEh0qBx0qKjosAigfHSoqOiwCKPUeKio8KAIsuh0qKjosAij1HioqPCgCLPIeKio8KAIsuh4qKjwoAizyHioqPCgCLP5w1h0qAi4b1h0qAi4Bxx4qKjwoAizPjw4WFg6PDhYWpR4qKjwoAiwBgvymHSoqHQNaHSoqAAIAAP/4AjsDLwAWAC8AJEAhAAMAA28AAAEAbwABAgIBVAABAQJYAAIBAkwcFBoZBAUYKyU0JyIvAS4BJyYiBw4CDwEGFRQWMjYlFA4BJic0NzY/AT4BNz4BHgEXHgMXFgEeCwEIDgYQBAIUAQQQDAgJCyo6LAEcpu6mAS0EHzgZPg8FHB4cBBA+MEADLc8UEwwVCSAMCQkNHhQLDBMUHSoqZXemAqp1UUgFLlQmejQQFAIQEjR6TFwFRwAAAgAA/7EEdwMLAAUAHwBLQEgYCwIEBRcSEAMDBBEBAgMDRwABBQFvAAUEBW8ABAMEbwADAgNvBgECAAACUgYBAgIAVgAAAgBKAAAdGxUUDg0ABQAFEREHBRYrBRUhETMRARUUBi8BAQYiLwEHJwE2Mh8BAScmNjsBMhYEd/uJRwPoFApE/p8GDgaC6GsBRwUOBoIBA0MJCA3zBwoHSANa/O4CuPIMCglE/p8GBoLpbAFGBgaCAQNECBYKAAAD//wAAARHAmoAEQAvAFoAZkBjBAEKAgFHAAMKCQoDCW0ABwkFCQcFbQwBBAsBAgoEAmAACgAJBwoJYAgBBQAABVQIAQUFAFgGDgENBAAFAEwUEgEAV1VOTUlIREI/Pjo5NTQsKiYlIB8bGhIvFC8AEQERDwUUKzciJjcRBwYuATY/ATYWFxEUBikBIiY/ATY0JiIGFRQGIiY1NDc2MhYUDwEzMhYOAQEWFRQOASY3NDYyFgcUFjI2NCYHIiY0NjcyPgEmJyIHDgEuATc2MzIWBxSdFSABHRQqEg4UZxwwASABwP78IR4Z0RQoOioeKiA0MpJlM3iHFSACHAGHN2SKZgEgKCIBJjYmJhsVICAVEBYCGg4ZCgoqJBALKlY7VAFZIBUBTA8KDigqCDMOIhr+YBUgQBnRFDsoJx8WHh4WSDMyZZAzeB4qIAElM0lGYgJmRBUgIBUbJiY2KAEeLBwCFiIUAhYSDhYoE05WOi4AAAUAAP/5A+QDCwAGAA8AOQA+AEgBB0AVQD47EAMCAQcABDQBAQACR0EBBAFGS7AKUFhAMAAHAwQDBwRtAAAEAQEAZQADAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTBtLsAtQWEApAAAEAQEAZQcBAwAEAAMEYAgBAQAGBQEGXwAFAgIFVAAFBQJYAAIFAkwbS7AXUFhAMAAHAwQDBwRtAAAEAQEAZQADAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTBtAMQAHAwQDBwRtAAAEAQQAAW0AAwAEAAMEYAgBAQAGBQEGXwAFAgIFVAAFBQJYAAIFAkxZWVlAFgAAREM9PDEuKSYeGxYTAAYABhQJBRUrJTcnBxUzFQEmDwEGFj8BNhMVFAYjISImNRE0NjchMhceAQ8BBicmIyEiBgcRFBYXITI2PQE0PwE2FgMXASM1AQcnNzYyHwEWFAHwQFVANQEVCQnECRIJxAkkXkP+MENeXkMB0CMeCQMHGwgKDQz+MCU0ATYkAdAlNAUkCBg3of6JoQJvM6EzECwQVRC9QVVBHzYBkgkJxAkSCcQJ/r5qQ15eQwHQQl4BDgQTBhwIBAM0Jf4wJTQBNiRGBwUkCAgBj6D+iaABLjShNA8PVRAsAAIAAP/5AoMDCwAHAB8AKkAnBQMCAAECAQACbQACAm4ABAEBBFQABAQBWAABBAFMIxMlNhMQBgUaKxMhNTQmDgEXBREUBgchIiYnETQ2FzM1NDYyFgcVMzIWswEdVHZUAQHQIBb96RceASAWEZTMlgISFx4BpWw7VAJQPaH+vhYeASAVAUIWIAFsZpSUZmweAAEAAP/5A6EDDAAlADBALQQBAgEAAQIAbQAAAwEAA2sAAwNuAAUBAQVUAAUFAVgAAQUBTBMlNSMVJAYFGisBFRQGByMiJj0BNCYOAQcVMzIWFxEUBgchIiYnETQ2FyE1ND4BFgOhFg4kDhZSeFIBNRceASAW/ekXHgEgFgF3ktCQAhGPDxQBFg6PO1QCUD1sHhf+vhYeASAVAUIWIAFsZ5IClgAABgAA/7EDEgMLAA8AHwAvADsAQwBnAGRAYVdFAgYIKSEZEQkBBgABAkcFAwIBBgAGAQBtBAICAAcGAAdrAA4ACQgOCWAPDQIIDAoCBgEIBl4ABwsLB1QABwcLWAALBwtMZWRhXltZU1JPTElHQT8UJBQmJiYmJiMQBR0rAREUBisBIiY1ETQ2OwEyFhcRFAYrASImNRE0NjsBMhYXERQGKwEiJjURNDY7ATIWExEhERQeATMhMj4BATMnJicjBgcFFRQGKwERFAYjISImJxEjIiY9ATQ2OwE3PgE3MzIWHwEzMhYBHgoIJAgKCggkCAqPCggkCAoKCCQICo4KByQICgoIJAcKSP4MCAgCAdACCAj+ifobBAWxBgQB6woINjQl/jAlNAE1CAoKCKwnCSwWshcqCSetCAoBt/6/CAoKCAFBCAoKCP6/CAoKCAFBCAoKCP6/CAoKCAFBCAoK/mQCEf3vDBQKChQCZUEFAQEFUyQICv3vLkRCLgITCggkCApdFRwBHhRdCgABAAAAAQAATL/dcF8PPPUACwPoAAAAANPJ41IAAAAA08njUv/8/2kEdwNSAAAACAACAAAAAAAAAAEAAANS/2oAAAR2//z//wR3AAEAAAAAAAAAAAAAAAAAAAAWA+gAAAOp//8CggAAAoIAAAOqAAAD3gAAA94AAAPoAAAD3gAAA94AAANIAAAD6AAAA+gAAAIwAAAD6AAAAjsAAAR2AAAER//8A+gAAAKCAAADoAAAAxEAAAAAAAAAuAEYAXgB1gJGAswDQAPeBJIE0AUSBUIF8Ab6B1gHtghuCWQJrAn+CsIAAAABAAAAFgCGAA0AAAAAAAIANABEAHMAAACSC3AAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEACAA1AAEAAAAAAAIABwA9AAEAAAAAAAMACABEAAEAAAAAAAQACABMAAEAAAAAAAUACwBUAAEAAAAAAAYACABfAAEAAAAAAAoAKwBnAAEAAAAAAAsAEwCSAAMAAQQJAAAAagClAAMAAQQJAAEAEAEPAAMAAQQJAAIADgEfAAMAAQQJAAMAEAEtAAMAAQQJAAQAEAE9AAMAAQQJAAUAFgFNAAMAAQQJAAYAEAFjAAMAAQQJAAoAVgFzAAMAAQQJAAsAJgHJQ29weXJpZ2h0IChDKSAyMDE2IGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21mb250ZWxsb1JlZ3VsYXJmb250ZWxsb2ZvbnRlbGxvVmVyc2lvbiAxLjBmb250ZWxsb0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABDACkAIAAyADAAMQA2ACAAYgB5ACAAbwByAGkAZwBpAG4AYQBsACAAYQB1AHQAaABvAHIAcwAgAEAAIABmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQBmAG8AbgB0AGUAbABsAG8AUgBlAGcAdQBsAGEAcgBmAG8AbgB0AGUAbABsAG8AZgBvAG4AdABlAGwAbABvAFYAZQByAHMAaQBvAG4AIAAxAC4AMABmAG8AbgB0AGUAbABsAG8ARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXAAxoZWxwLWNpcmNsZWQPYW5nbGUtZG91YmxlLXVwEWFuZ2xlLWRvdWJsZS1kb3duBG1lbnUKYmF0dGVyeS0yNQpiYXR0ZXJ5LTUwA2NvZwpiYXR0ZXJ5LTc1C2JhdHRlcnktMTAwDmNhbmNlbC1jaXJjbGVkBnZvbHVtZQRwbHVzCWhvdXJnbGFzcwRjYWxjBHRpbnQKY2hhcnQtbGluZQxzb3J0LW51bWVyaWMEZWRpdARsb2NrCWxvY2stb3Blbgt0cmFzaC1lbXB0eQAAAAAAAAEAAf//AA8AAAAAAAAAAAAAAAAAAAAAABgAGAAYABgDUv9pA1L/abAALCCwAFVYRVkgIEu4AA5RS7AGU1pYsDQbsChZYGYgilVYsAIlYbkIAAgAY2MjYhshIbAAWbAAQyNEsgABAENgQi2wASywIGBmLbACLCBkILDAULAEJlqyKAEKQ0VjRVJbWCEjIRuKWCCwUFBYIbBAWRsgsDhQWCGwOFlZILEBCkNFY0VhZLAoUFghsQEKQ0VjRSCwMFBYIbAwWRsgsMBQWCBmIIqKYSCwClBYYBsgsCBQWCGwCmAbILA2UFghsDZgG2BZWVkbsAErWVkjsABQWGVZWS2wAywgRSCwBCVhZCCwBUNQWLAFI0KwBiNCGyEhWbABYC2wBCwjISMhIGSxBWJCILAGI0KxAQpDRWOxAQpDsAFgRWOwAyohILAGQyCKIIqwASuxMAUlsAQmUVhgUBthUllYI1khILBAU1iwASsbIbBAWSOwAFBYZVktsAUssAdDK7IAAgBDYEItsAYssAcjQiMgsAAjQmGwAmJmsAFjsAFgsAUqLbAHLCAgRSCwC0NjuAQAYiCwAFBYsEBgWWawAWNgRLABYC2wCCyyBwsAQ0VCKiGyAAEAQ2BCLbAJLLAAQyNEsgABAENgQi2wCiwgIEUgsAErI7AAQ7AEJWAgRYojYSBkILAgUFghsAAbsDBQWLAgG7BAWVkjsABQWGVZsAMlI2FERLABYC2wCywgIEUgsAErI7AAQ7AEJWAgRYojYSBksCRQWLAAG7BAWSOwAFBYZVmwAyUjYUREsAFgLbAMLCCwACNCsgsKA0VYIRsjIVkqIS2wDSyxAgJFsGRhRC2wDiywAWAgILAMQ0qwAFBYILAMI0JZsA1DSrAAUlggsA0jQlktsA8sILAQYmawAWMguAQAY4ojYbAOQ2AgimAgsA4jQiMtsBAsS1RYsQRkRFkksA1lI3gtsBEsS1FYS1NYsQRkRFkbIVkksBNlI3gtsBIssQAPQ1VYsQ8PQ7ABYUKwDytZsABDsAIlQrEMAiVCsQ0CJUKwARYjILADJVBYsQEAQ2CwBCVCioogiiNhsA4qISOwAWEgiiNhsA4qIRuxAQBDYLACJUKwAiVhsA4qIVmwDENHsA1DR2CwAmIgsABQWLBAYFlmsAFjILALQ2O4BABiILAAUFiwQGBZZrABY2CxAAATI0SwAUOwAD6yAQEBQ2BCLbATLACxAAJFVFiwDyNCIEWwCyNCsAojsAFgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAULLEAEystsBUssQETKy2wFiyxAhMrLbAXLLEDEystsBgssQQTKy2wGSyxBRMrLbAaLLEGEystsBsssQcTKy2wHCyxCBMrLbAdLLEJEystsB4sALANK7EAAkVUWLAPI0IgRbALI0KwCiOwAWBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsB8ssQAeKy2wICyxAR4rLbAhLLECHistsCIssQMeKy2wIyyxBB4rLbAkLLEFHistsCUssQYeKy2wJiyxBx4rLbAnLLEIHistsCgssQkeKy2wKSwgPLABYC2wKiwgYLAQYCBDI7ABYEOwAiVhsAFgsCkqIS2wKyywKiuwKiotsCwsICBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4IyCKVVggRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOBshWS2wLSwAsQACRVRYsAEWsCwqsAEVMBsiWS2wLiwAsA0rsQACRVRYsAEWsCwqsAEVMBsiWS2wLywgNbABYC2wMCwAsAFFY7gEAGIgsABQWLBAYFlmsAFjsAErsAtDY7gEAGIgsABQWLBAYFlmsAFjsAErsAAWtAAAAAAARD4jOLEvARUqLbAxLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2E4LbAyLC4XPC2wMywgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhsAFDYzgtsDQssQIAFiUgLiBHsAAjQrACJUmKikcjRyNhIFhiGyFZsAEjQrIzAQEVFCotsDUssAAWsAQlsAQlRyNHI2GwCUMrZYouIyAgPIo4LbA2LLAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjILAIQyCKI0cjRyNhI0ZgsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhIyAgsAQmI0ZhOBsjsAhDRrACJbAIQ0cjRyNhYCCwBEOwAmIgsABQWLBAYFlmsAFjYCMgsAErI7AEQ2CwASuwBSVhsAUlsAJiILAAUFiwQGBZZrABY7AEJmEgsAQlYGQjsAMlYGRQWCEbIyFZIyAgsAQmI0ZhOFktsDcssAAWICAgsAUmIC5HI0cjYSM8OC2wOCywABYgsAgjQiAgIEYjR7ABKyNhOC2wOSywABawAyWwAiVHI0cjYbAAVFguIDwjIRuwAiWwAiVHI0cjYSCwBSWwBCVHI0cjYbAGJbAFJUmwAiVhuQgACABjYyMgWGIbIVljuAQAYiCwAFBYsEBgWWawAWNgIy4jICA8ijgjIVktsDossAAWILAIQyAuRyNHI2EgYLAgYGawAmIgsABQWLBAYFlmsAFjIyAgPIo4LbA7LCMgLkawAiVGUlggPFkusSsBFCstsDwsIyAuRrACJUZQWCA8WS6xKwEUKy2wPSwjIC5GsAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xKwEUKy2wPiywNSsjIC5GsAIlRlJYIDxZLrErARQrLbA/LLA2K4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrErARQrsARDLrArKy2wQCywABawBCWwBCYgLkcjRyNhsAlDKyMgPCAuIzixKwEUKy2wQSyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbErARQrLbBCLLA1Ky6xKwEUKy2wQyywNishIyAgPLAEI0IjOLErARQrsARDLrArKy2wRCywABUgR7AAI0KyAAEBFRQTLrAxKi2wRSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKyAAEAKrEABUKzCgIBCCqxAAVCsw4AAQgqsQAGQroCwAABAAkqsQAHQroAQAABAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbMMAgEMKrgB/4WwBI2xAgBEAAA=') format('truetype'); + src: url('data:application/octet-stream;base64,d09GRgABAAAAABmIAA8AAAAAKsgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIslek9TLzIAAAGUAAAAQwAAAFY+IUkiY21hcAAAAdgAAACmAAACiOnFAFpjdnQgAAACgAAAABMAAAAgBtX/AmZwZ20AAAKUAAAFkAAAC3CKkZBZZ2FzcAAACCQAAAAIAAAACAAAABBnbHlmAAAILAAADcEAABWEVKl1aWhlYWQAABXwAAAAMgAAADYTyTiUaGhlYQAAFiQAAAAgAAAAJAfIA/ZobXR4AAAWRAAAADoAAABYTOP/+2xvY2EAABaAAAAALgAAAC47fjW8bWF4cAAAFrAAAAAgAAAAIAFdDDxuYW1lAAAW0AAAAXcAAALNzJ0eIHBvc3QAABhIAAAAxAAAAR260xEFcHJlcAAAGQwAAAB6AAAAhuVBK7x4nGNgZGBg4GIwYLBjYHJx8wlh4MtJLMljkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAJjsFSAB4nGNgZK5nnMDAysDAVMW0h4GBoQdCMz5gMGRkAooysDIzYAUBaa4pDA4vGF6IMAf9z2KIYg5imA4UZgTJAQDp2AvBAHic7ZLbDQIxDATnuPA+3lwXVEJBfFHwdgHrsGUQaSzFcqJoJ8ASGM3DNBjeDNR6uTv0/siu9xvPPtOqr/vn40pV71uvC88237hizYatz+2ZOHDkxJkLV27cmT264r+mKsOU3Vxp/uj5BmeKQnlSqMQVyqGCs0fBFlCwDxRsBoVyq2BbKNTrFGwQBbtEwVZRsF8UbBoFO0fB9lHwP/Af+sH8BeUZMdoAAHicY2BAAxIQyBz0PxOEARJmA9sAeJytVml300YUHXlJnIQsJQstamHExGmwRiZswYAJQbJjIF2crZWgixQ76b7xid/gX/Nk2nPoN35a7xsvJJC053Cak6N3583VzNtlElqS2AvrkZSbL8XU1iaN7DwJ6YZNy1F8KDt7IWWKyd8FURCtltq3HYdERCJQta6wRBD7HlmaZHzoUUbLtqRXTcotPekuW+NBvVXffho6yrE7oaRmM3RoPbIlVRhVokimPVLSpmWo+itJK7y/wsxXzVDCiE4iabwZxtBI3htntMpoNbbjKIpsstwoUiSa4UEUeZTVEufkigkMygfNkPLKpxHlw/yIrNijnFawS7bT/L4vead3OT+xX29RtuRAH8iO7ODsdCVfhFtbYdy0k+0oVBF213dCbNnsVP9mj/KaRgO3KzK90IxgqXyFECs/ocz+IVktnE/5kkejWrKRE0HrZU7sSz6B1uOIKXHNGFnQ3dEJEdT9kjMM9pg+Hvzx3imWCxMCeBzLekclnAgTKWFzNEnaMHJgJWWLKqn1rpg45XVaxFvCfu3a0ZfOaONQd2I8Ww8dWzlRyfFoUqeZTJ3aSc2jKQ2ilHQmeMyvAyg/oklebWM1iZVH0zhmxoREIgIt3EtTQSw7saQpBM2jGb25G6a5di1apMkD9dyj9/TmVri501PaDvSzRn9Wp2I62AvT6WnkL/Fp2uUiRen66Rl+TOJB1gIykS02w5SDB2/9DtLL15YchdcG2O7t8yuofdZE8KQB+xvQHk/VKQlMhZhViFZAYq1rWZbJ1awWqcjUd0OaVr6s0wSKchwXx76Mcf1fMzOWmBK+34nTsyMuPXPtSwjTHHybdT2a16nFcgFxZnlOp1mW7+s0x/IDneZZntfpCEtbp6MsP9RpgeVHOh1jeUELmnTfwZCLMOQCDpAwhKUDQ1hegiEsFQxhuQhDWBZhCMslGMLyYxjCchmGsLysZdXUU0nj2plYBmxCYGKOHrnMReVqKrlUQrtoVGpDnhJulVQUz6p/ZaBePPKGObAWSJfIml8xzpWPRuX41hUtbxo7V8Cx6m8fjvY58VLWi4U/Bf/V1lQlvWLNw5Or8BuGnmwnqjapeHRNl89VPbr+X1RUWAv0G0iFWCjKsmxwZyKEjzqdhmqglUPMbMw8tOt1y5qfw/03MUIWUP34NxQaC9yDTllJWe3grNXX27LcO4NyOBMsSTE38/pW+CIjs9J+kVnKno98HnAFjEpl2GoDrRW82ScxD5neJM8EcVtRNkja2M4EiQ0c84B5850EJmHqqg3kTuGGDfgFYW7BeSdconqjLIfuRezzKKT8W6fiRPaoaIzAs9kbYa/vQspvcQwkNPmlfgxUFaGpGDUV0DRSbqgGX8bZum1Cxg70Iyp2w7Ks4sPHFveVkm0ZhHykiNWjo5/WXqJOqtx+ZhSX752+BcEgNTF/e990cZDKu1rJMkdtA1O3GpVT15pD41WH6uZR9b3j7BM5a5puuiceel/TqtvBxVwssPZtDtJSJhfU9WGFDaLLxaVQ6mU0Se+4BxgWGNDvUIqN/6v62HyeK1WF0XEk307Ut9HnYAz8D9h/R/UD0Pdj6HINLs/3mhOfbvThbJmuohfrp+g3MGutuVm6BtzQdAPiIUetjrjKDXynBnF6pLkc6SHgY90V4gHAJoDF4BPdtYzmUwCj+Yw5PsDnzGHQZA6DLeYw2GbOGsAOcxjsMofBHnMYfMGcdYAvmcMgZA6DiDkMnjAnAHjKHAZfMYfB18xh8A1z7gN8yxwGMXMYJMxhsK/p1jDMLV7QXaC2QVWgA1NPWNzD4lBTZcj+jheG/b1BzP7BIKb+qOn2kPoTLwz1Z4OY+otBTP1V050h9TdeGOrvBjH1D4OY+ky/GMtlBr+MfJcKB5RdbD7n74n3D9vFQLkAAQAB//8AD3icrVh7bFvXeT/fueeee3lJXr7uQyKpK4qUSJmiKIVPx6JpSpathxknsWmFVhWFdiQ5taw4wOJHkyBovQyBByz2gqBLhbWxgyR/BJnXdH90K9A0aFqkGArEAYo+5mB/DUOKDenQbcay2fS+e0knbubNSFY9zj33u+ec+72/33cJu3HjxlnhdSFPAmSA7CR1cv67+WRCEBnM7Pm2cl+zViJcYAJnG4SJAhPXiSRTKq0SQgROhBYRiSyJ8iFCAWiDUAoHCVC4J1Ir32ajLNGNO+08WPOEQmNR3ewxB11i30igUKVmzqKqEM/SsmbRqlAuZCGuQtks5XSN+wD/VOiDfLFQKldhB9hjuZBMxAXNyJWylM8//cahhy49VQecvI3Xk1uXz5z/aqsEyy+/9crSL/pHDzHgIt1qeE5wATQp5OEhDRq1/YH0zjRN7h6G5Zvbf3Dp6fn5py89NPPM0jZaan117qGXl5dfvn+1z2QyoNr6POGeobDAKcjMLfHAcOKu6fafxOLxWhwGCUXpCW3Sh4lORshIbbgPqCiBQOkMEal4nFCBHmcgEOE4ACFrptZjahoXwyNDGsoZHwPJHpKFMpRyJhjF21Jpk0dFlX9wRfSzKOfA8AYYvy3xpIRk8coVUYxymyCK7Wv8PYf4wQc3iZwj0WG9y/+Jm/wPgQA+IAKdEYEBO47GFOA4sQXAlWua2aOZDv+oVeQ0hUyifXL9ULYH5P92VOS/fQ3ZxJc6PF/5oMPz7YjwmGQzZzOpfsr0ezZRFDtEzq9cwT22AAIhXX/3kB4ySKZqO2RgqHomtiSg+JwKLQ4Eld/AC4EmwZt6uFf1Aon19Q6GE1rA26Oaskg84HaJ2shQzkAnTAzEk4WSCTlD4ylnXnbmSM+iPwpT6Wolcz2ZqVTT9O8ylc68krHn1fT15EgFKpnFymi6Wl2oAhyrNqpQWagAbFQPIKkCHd5x+Hv6Z8h7nOTIdG0Stc0J8rkhyJRInEjrGFUcRN4iEmNSg0gSaxImsTqQu7JbkgNWr6GFVC8u9YDH5r4cT26HQiln9IHGkWMTZUl1REgFCqX8QM4QAoUk3nMzoBl4X3pfj8XSlkXPZSpQHVmqXX9nchGWanT75OIRy7j+jh4Dy6Db9dgPLeN93bJ03AB3V0YeyVRhcgkWp2Dr5NJS+1nYalgQ09s/wTW2aOwW+XrJGNlB7q3VmYCBRGQUUXRThRGXTFyYSpAiyS3i4tzVIC4XbxLu4nXbSNWJUj47kkwMxCJhLeTtVXs7wnpsU8WT5aItrCPl55ccNmzZMlGw3vwiOvgc6sA4u/Fl4UPhfpIi42S+NjM0mIhHwr09MgVdA4Jq8ftUiYtMIoIwE8Cg242JVAAqbKCjYAJZRx/2zKJGvWRuLDuS7rfCTNRHQJe4ZEopTJmJVDIlpcrJMSiWS+UUZtMdkDcNs2xKhikVkphhMXsKH/6N4j699pLWb52L6MbF1a+45XfflTynVy/4YtFzUSP8rbVTXunlhdMN2nx8EV59Lhwxv7lyUnH/8MeS+9TKBb03+lxU73lp5ZTseecdyXNi5c8j+sOVRuN0o4HeeqvNk6SCFWix9gAXBQEUoqDZJZV5qYcTt0Lc68SFNJfSIm5ZdjeI2y03ieyW65Gwbfo9sztrE1vz49nR4dRALJyMJG91AN//6QCfPPl/eAMkHAJOFr6gW3yugOGf0V2ezJGDZLV2GMuqwMCDQpMNV4D7mY+qMvF6iHeduO205WkRr6J4G8TrVZpE8Sr14VRHhc0D990zu6u2fWJbsZAdTeWH8/9TkcE7KTL1iSJTvw+tvm1LjZPv/B61+3kUbcfi94RZgRKZaGS4NqSFggGMPq/HrbixOsxQHMhxjLc1ICLD1CQLqCIU189jSX8JyhhmGHMYWGV45dLVq+1Xrl4Ftrmw+WJzc7P54uaCQDu0pavtpc2FF1/EJ/bYrVcbmAfuxUhRiUHStZQL7HeGgorAyC5DZQJME/v9WAtgDbeIRAwIGOnFQB4CMFQqpMqGqUlmyY8Tv/TPcLj9LbgwNzfzZbo+9uST9c3N5+ACDFyzYCAx/9P6/NEX/nR17BS0npzfbNc3iX02uXEUediHzpOtjdjSCljxNxAwoZsJrJtpGPOyOVzuJu6A/SOJvcjDwCe/cATCcKT9jfY/vn9z0v4GHOnIeJSO4/kRMkwOk121qWKOUqEEjDbvoQgjERsxzkS+gT7POKY2ipiSshamPrtAf5Ln9u8LRwZTfWHJwRsqaBbYjue4ZxUQNHbcVBclbhr5XLmUQowYQnyJ4KNYRhxiav2CkTQSmkp9lCd5kfdBlRYLCDMtMIuFMYrZMgt0vHX4cOuNX75hXy797FLbCjJ56alnmqrVOnr6vBz19PYt0ZV/W2GLo6FAwGN5qMJlj89H44vy49OFqSf40kjQK3okRh/NPLJcWVrLnozv3Rs/mV1bqiw/kjkZ27s3xlWZF3KKVmrPB61BtSe6ZOVKpVxscSSk63RcCQZVxef30zgsxqZYcjxuWfHxJJuylraEVFnzGI5SbNtdRN3KiPDDJEEypEiqZIbsJ0vkGDlB/oh8rfb0g1hUNho7RplbKQ0GEf2jynUjqAqKW1daZsgnuP0eGVG8m7W8LipySgmIpEU0rbdJens9swEJbQE9TdLT4+2Ze+ZrT33l8T947Pj6Iw+3vrT4wMK+++t7du+amtxeuXtrIT8+NpLWQp3fYBghvonFhlvggHa76lQh5VBoSbyVoIIFpd990iU4E8CUBVyyTVUSbhIx/SAWiKMdO+lF6m4PBTqYrdhNQz9Pb8vQ4WryV+nKMM1szf6qew9/2SX88k4P4PX0tjRktmZ+0SX8TPNfL6mGodK/9RnQ3TafiV77qC+d7hOCfWkJJza5v3v9dyvt7P3eZ+5/e5P+mfv2Yz/vS9NsxBnh3S71vXP2S+3h1S4Fzlx7zXnnIo5OPvsPOiGMYT4ZI4O1gS5ad3IYtmENjCjaRDRB61Et3MvEnpGhQipuO34qGZcwNIDrdmM1hCGWTBXKiBvQdFVUt2kJpgGWG2QVyw7VgKHbuNwoDERf++g1GGX9W3urPjFqRZm/Ol4TRt/TQh7dFfO4XF5Lc3tCGnK4euo1+vqJ+2fFbCP5RMGvUX+w8ER9SZzp5OI32SnBjWmun+ypzfa4EbqbQb8gsACCH/RbELH/EBn2Ukxg2IlQ4ThHZE/oPrxQsmAfMk9IX0TXVK+TLTFVGSOiPhDIBzAO+Bh0WqqU05GgzKUJzOXs1H+dnRE+1JTp9kvYVJz5cB1msAs5A8JOl+z9V0mRZoXFax/Rv/6tR3HZa/iZXx+D3dxeMS0bCua5G9cQ5M7QoxiJY2SRrNUeZqBQmCGC4lIE1waRXKJLEjc8wNyAYJe2iEJckuJqYWeJib2BF5E0uQpebFlIXQsCeeDA3nvmZqendlS33V0sZNLJoVh/JBwc08ZIAAK+DsIvBySOXQhmOQPLLN8C8STO7UjQ7exYRBNir+KDfM5QAbCdQbuW7Uwp2WYuJCWnn8lV0dpof/SDcilvP/wmls8+LR1UtSPRcYjB2+1r2GG8rw1vS1vpWCH3/Gr+9LN6jEbh2fLKH69BbDgOyVIyGcHKrvsNGlZ7FSU96HenFyYacDCmQ92nqMNpOa/Gw+2WHqvhYRPDqX7DsozZfG71fP40HgxD+bndh+natH1MJJksDduAgRpxjWLOMoZDexe2Ze1CSG58LPwD+gonPnI3Zr1ZkGp6rTrhx6ZCIqyAlZTOTGLfurvzrWOcSAITJLaBdsJnqxgYjAgM1Q+Ei8AfwkaRiohaRTs+RFqP7Pm2G/dt6a7HRv7OG8wv9KLaXbduYQQ27rjn4MGDNawB0zu3V+zez4oYIdQE11wIDoYQk+h5HZIYywb6Qkj/NB2WB3KmhU09TzmZU0JgZmLmxNppO5BgQqKIwdHp1DX4l9qBWhF0l+tHriD+Dy7vbI/vXF7eCZcTlkuQIrLi9bTHhwpQGoTLQwVxUO4pX2yfvUiP5y/m/Rn/Af/3Jw9M9pfg+ZtHtN862jlgahlUFuJRmQmFoe4ZuyU8QYZzF9pnL0C2cLHg8x3wZ5y88DH9Q7S1hHkBUZKIcA0QqtANQumjtpZZAyGKDRcY1BOhoVLIb3+WCA1ggVDBFDEupC4sNfMOZkUF5Yy/gr7GyQbA5Zhx/deolpgReOGnX6dBnL56bKJB79t+sf2WgxZhyojBsbUXXlg7ZjmY6WOsux4yRMZro8xmBZMQGs+GTusIXB4lIoBjLpsnEeqhoWJCH3Q+laAxpISDWpA1hw3zVvaQ5SoYwkVDHVSNfaf3QdFhrMsfnHr+8nkaOOfTAKuAw+Ixy/wdJo88T7/u9A833hSCqDMf6myMTJCd5AhZqR16YBfl8paB3oALOHI9g7rsfmZg9OZnBhU7cdXV8nmp7FEoB5kvE8ntdj45uJvELbnrqyuHlr90cP++e+tzM5M7tEEtaf8k/FjwAcUZgY672cnVvMN9CPEjlnUs1FWAPPatCS6Jur2mW8ZTgY6+cLX97Q811m9nM7AUeVBWnOHcp9PnFKkzlZTZtkfGnvoyleX22f+MMPFNzuCfFLnU9biive6NlCtjfMdMu1J/ISvw3fYPbCJM2uP/Mm+v0MD133g0RdHo6qRt6f34xuu/yU5PZWnIYeJBHXsc7UHlvwFt9oFbAAAAeJxjYGRgYABi6+qaU/H8Nl8ZuJlfAEUYbkhXVcHo/3/+Z7KUMwcBuRwMTCBRAFtvDJ8AAHicY2BkYGAO+p/FwMBS9v/P//8s5QxAERQgBgCjFAbFeJxjfsHAwLzy/3+mJgYGEGZeBcT3oPgFEtsDygdiJgMobc3AwFIGxO7//4D5IP0LgFiQgQEAfdISGwAAAAAAAAC4ARgBeAHWAkYCzANAA94EkgTQBRIFQgXwBvoHWAe2CG4JZAmsCf4KwgAAAAEAAAAWAIYADQAAAAAAAgA0AEQAcwAAAJILcAAAAAB4nHWQ3WrCMBiG38yfbQrb2GCny9FQxuoPDEQQBIeebCcyPB211rZSG0mj4G3sHnYxu4ldy17bOIayljTP9+TLl68BcI1vCOTPE0fOAmeMcj7BKXqWC/TPlovkF8slVPFmuUz/brmCBwSWq7jBByuI4jmjBT4tC1yJS8snuBB3lgv0j5aL5J7lEm7Fq+UyvWe5golILVdxL74GarXVURAaWRvUZbvZ6sjpViqqKHFj6a5NqHQq+3KuEuPHsXI8tdzz2A/Wsav34X6e+DqNVCJbTnOvRn7ia9f4s131dBO0jZnLuVZLObQZcqXVwveMExqz6jYaf8/DAAorbKER8apCGEjUaOuc22iihQ5pygzJzDwrQgIXMY2LNXeE2UrKuM8xZ5TQ+syIyQ48fpdHfkwKuD9mFX20ehhPSLszosxL9uWwu8OsESnJMt3Mzn57T7HhaW1aw127LnXWlcTwoIbkfezWFjQevZPdiqHtosH3n//7AelzhFMAeJxtjetKAzEUhDNtrO7FXrzUpwiswuLzpGcPu8FsEnJR+vauSAuC82PmGxgYsRK/qsX/OmKFNSRusMEt7lChRoMW99hihz0OeMAjnvCMI15EO7ENikwky8NOu9GyGnw5LVHC4U8f/JeTM7tSn3TOHM/qrb9i363Jj9f63jcXfO26LWlHbC83m09vy8wy2JKqyZc4Wp2SJG1JZuNyTZOOWVnjuE1+IbesoyHJg8nSevqofkz5wK7JUadJ8RzyWYhvVYpJbnicY/DewXAiKGIjI2Nf5AbGnRwMHAzJBRsZWJ02MTAyaIEYm7mYGDkgLD4GMIvNaRfTAaA0J5DN7rSLwQHCZmZw2ajC2BEYscGhI2Ijc4rLRjUQbxdHAwMji0NHckgESEkkEGzmYWLk0drB+L91A0vvRiYGFwAMdiP0AAA=') format('woff'), + url('data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCLJXoAAAD8AAAAVE9TLzI+IUkiAAABUAAAAFZjbWFw6cUAWgAAAagAAAKIY3Z0IAbV/wIAAB6wAAAAIGZwZ22KkZBZAAAe0AAAC3BnYXNwAAAAEAAAHqgAAAAIZ2x5ZlSpdWkAAAQwAAAVhGhlYWQTyTiUAAAZtAAAADZoaGVhB8gD9gAAGewAAAAkaG10eEzj//sAABoQAAAAWGxvY2E7fjW8AAAaaAAAAC5tYXhwAV0MPAAAGpgAAAAgbmFtZcydHiAAABq4AAACzXBvc3S60xEFAAAdiAAAAR1wcmVw5UErvAAAKkAAAACGAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDfwGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgA6BQDUv9qAFoDUgCXAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAF8AAEAAAAAAHYAAwABAAAALAADAAoAAAF8AAQASgAAAAQABAABAADoFP//AADoAP//AAAAAQAEAAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAQwAAAAAAAAAFQAA6AAAAOgAAAAAAQAA6AEAAOgBAAAAAgAA6AIAAOgCAAAAAwAA6AMAAOgDAAAABAAA6AQAAOgEAAAABQAA6AUAAOgFAAAABgAA6AYAAOgGAAAABwAA6AcAAOgHAAAACAAA6AgAAOgIAAAACQAA6AkAAOgJAAAACgAA6AoAAOgKAAAACwAA6AsAAOgLAAAADAAA6AwAAOgMAAAADQAA6A0AAOgNAAAADgAA6A4AAOgOAAAADwAA6A8AAOgPAAAAEAAA6BAAAOgQAAAAEQAA6BEAAOgRAAAAEgAA6BIAAOgSAAAAEwAA6BMAAOgTAAAAFAAA6BQAAOgUAAAAFQAE////iQOqAzMAEQAhAEMATACQtzMmIwMFBAFHS7AKUFhANgAGAwQDBgRtAAQFAwQFawAHCAICB2UAAAADBgADYAAFAAgHBQhhAAIBAQJUAAICAVkAAQIBTRtANwAGAwQDBgRtAAQFAwQFawAHCAIIBwJtAAAAAwYAA2AABQAIBwUIYQACAQECVAACAgFZAAECAU1ZQAwTEy8cFRcYFyQJBR0rETQ+AhcyHgIOAyIuAjcUHgI+Azc0LgEiDgE3FzYyFRQGDwEGDwEOAR0BMzU0Njc+AT8BNjc+ATc0JiMiAxQWMjYuAgZKfqxhX658TAFKfqzArnxMdjhegpCAYDYBXqK+pFzXHy1hBAEGBQI4Fgx1BgMBFAcTDAYTFAFUQFMRKkMqAiZGKAFeX658TAFKfqy/rn5KSn6uX0eEXDoCNmCASV+iXl6iUWUdFwQIAQUEAR0MGhglGgMGAgEIBAsHBhEoIzFE/o0gIiJAIgEkAAIAAAAAAlgCYwAVACsAK0AoHQECBQcBAwICRwAFAgVvAAIDAm8EAQMAA28BAQAAZhcUGBcUFAYFGislFA8BBiIvAQcGIi8BJjQ3ATYyFwEWNRQPAQYiLwEHBiIvASY0NwE2MhcBFgJYBhwFDgbc2wUQBBwGBgEEBQ4GAQQGBhwFDgbc2wUQBBwGBgEEBQ4GAQQGdgcGHAUF29sFBRwGDgYBBAUF/vwGzwcGHAUF3NwFBRwGDgYBBAYG/vwGAAAAAAIAAAAAAlgCdQAVACsAK0AoJQEDAQ8BAAMCRwUBBAEEbwIBAQMBbwADAANvAAAAZhQXGBQXFAYFGisBFAcBBiInASY0PwE2Mh8BNzYyHwEWNRQHAQYiJwEmND8BNjIfATc2Mh8BFgJYBv78BRAE/vwGBhwFDgbb3AUQBBwGBv78BRAE/vwGBhwFDgbb3AUQBBwGAXAHBv78BgYBBAYOBhwFBdzcBQUcBs8HBv78BQUBBAYOBhwGBtvbBgYcBgAAAAMAAP+JA6oDMwAMABgAJABCQD8IAQQABQIEBWAHAQIAAwACA2AGAQABAQBUBgEAAAFYAAEAAUwaGQ4NAQAgHRkkGiMUEQ0YDhcIBQAMAQsJBRQrJTIWFRQGIyEiJjQ2FwEyFhQGJyEiJjQ2NwEyFhQGIyEiLgE2NwNCKj48LP0mLDw+KgLaLDw8LP0mLDw8LALaLDw+Kv0mKzwBPCxaPC0qPj5WPgEBbD5UPgE8VjwBAW0+VT4+VjwBAAAAAwAAAAAD3gKXAAwAIgAyAERAQQIBAQYABgEAbQMIAgAHBgAHawAFAAYBBQZgAAcEBAdUAAcHBFgABAcETAEAMS4pJiEeGRYUEw4NBwYADAEMCQUUKzciJj0BNDYyFh0BFAYBMhYXFRQGJxQGJyEiJicRNDYzITIWAxE0JichIgYXERQWMyEyNtEVICAqHh4Cjyw8AT4rXED9w0FaAVxAAj1BWmceFv3DFSABHhYCPRUgwh4W0RUeHhXRFSABOTwraCw+AUFcAVpCAThBXFz+hwE4Fh4BIBX+yBUeHgAAAAAEAAAAAAPeApcADAAZAC8APwBPQEwEAwIBCAAIAQBtBQsCCgQACQgACWsABwAIAQcIYAAJBgYJVAAJCQZYAAYJBkwODQEAPjs2My4rJiMhIBsaFBMNGQ4ZBwYADAEMDAUUKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjYBbRUgAR4sHAEesRUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAgAA/2kD6ANRACcAMABKQEclJCMiGxoZGAgCARUUAQAEAwIQDw4HBgUEBwADA0cRAQMBRgACAQMBAgNtAAMAAQMAawABAQxIAAAADQBJLy4rKh8eGgQFFSsBFQcGBxcHJwYPASMnJicHJzcmLwE1NzY3JzcXNj8BMxcWFzcXBxYXBzQmIg4BFjI2A+i5Cgt4Zp8UHx6PGxUWoWV5CwjHxwcMeGWgDyAcjxwWGp5mdw0HolZ4VAJYdFoBpY4aGxedZHYKC8LFBwt3ZKAVGRyOHBUYn2R3CAzDwwcMdWScGxVjPFRUeFRUAAUAAAAAA94ClwAMABkAJgA8AEwAWkBXBgUDAwEKAAoBAG0HDgQNAgwGAAsKAAtrAAkACgEJCmAACwgIC1QACwsIWAAICwhMGxoODQEAS0hDQDs4MzAuLSgnISAaJhsmFBMNGQ4ZBwYADAEMDwUUKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGJSImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BIxUgAR4sHh4BViw8AT4rXED9w0FaAVxAAj1BWmceFv3DFSABHhYCPRUgwh4W0RUeHhXRFSABHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAAGAAAAAAPeApcADAAZACYAMwBJAFkAZUBiCAcFAwQBDAAMAQBtCREGEAQPAg4IAA0MAA1rAAsADAELDGAADQoKDVQADQ0KWAAKDQpMKCcbGg4NAQBYVVBNSEVAPTs6NTQuLSczKDMhIBomGyYUEw0ZDhkHBgAMAQwSBRQrJSImNzU0NjIWFxUUBiciJj0BNDYyFh0BFAYlIiYnNTQ2MhYdARQGJyImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BwBYeASAqHh6yFSABHiweHgFWLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAACAAD/ugNIAwIACAAUAChAJRQTEhEQDw4NDAsKCwEAAUcCAQABAG8AAQFmAQAFBAAIAQgDBRQrATIWEAYgJhA2ATcnBycHFwcXNxc3AaSu9vb+pPb2AQSaVpqYWJqaWJiaVgMC9v6k9vYBXPb+XJpWmJhWmphWmJhWAAAAAwAA/20D6ANPAAUADgAWACpAJwkBAQABRxMSCgMEAEUWDgQDAUQAAAEAbwIBAQFmAAAABQAFEQMFFSs1ETMBEQElNjQnNxYXFAcXNhAnNxYQB+wBYv6eAaBJSUdpAmsve3tMmpqOAaABIfweASEjSsxMSmqUkWUvdwFge0qa/kyaAAAAAQAA/2oD6ANSAAsALkArAgEAAQMBAANtBgUCAwQBAwRrAAEBDEgABAQNBEkAAAALAAsREREREQcFGSs1ESERIREhESERIREBZwEaAWf+mf7m0QEaAWf+mf7m/pkBZwAAAwAA/2oCMANSABsAKABiAEVAQjUyAgIDNgEEAlhNAgAGA0cABQQGBAUGbQAGAAQGAGsAAgAEBQIEYAADAwFYAAEBDEgAAAANAElTUhobJCcdGgcFGisBFA4BFB4BHQEUBiImPQE0PgE0LgE9ATQ2MhYVBQcGFxYzMjc2JyYjIhM0PgI/ATY1NwYiJxcUHwMWJhYjFA4CDwIGJgY1Bh0BPgI1NDIVFB4BFzU0LwImLwEuAQIwYGJiYKzYrGBiYmCu1K7+HhIECFx8hFgOHmBqeJAIHAwZHVwCZPRkBFotExERDB4MAgoGCAwPDwIiWgh0RDRCegZcKxINBQwHBAJuLGhePFxmLnYiTk4idi5mXDxeaCx2IE5OIAYOCAY0MgoUNv5KEh4kDhgcXB4yNjYyIForExUVAjAKEhIOCg8QEAIiAVogQgQmMCIeHiIwJgRCHlwpEw4IFAwWAAAADQAA/2oDoQNSAAgAEQAaACMALAA1AD4ARwBTAFwAbAB1AIUAgUB+XQEVFG1UPy0ECwo2JBIDBQQDRwAVFhIOAwoLFQpgFxMPAwsQDAgDBAULBGANCQIFBgICAAEFAGAAFBQZWAAZGQxIEQcDAwEBGFgAGBgNGEmEgXx5dHNwb2toY2BbWldWUlFMS0ZFQkE9PDk4NDMwLysqFBMUExQTFBMSGgUdKxc0JiIGHgE+ATc0JiIOARY+ASc0JiIGHgI2BTQmIg4BFj4BJzQmIg4BHgE2JzQmIgYeAjYFNCYiDgEeATYnNCYiDgEeATYBNTQuAQYHFRQeATYDNCYiDgEeATY3NTQmIyEiBh0BFBYzITI2BzQmIgYeAjYTERQGIyEiJjURNDYzITIW1io6LAIoPibZKjwoAiw4LtkqOiwCKD4mAa8qPCgCLDgu2Co8KAIsOC7ZKjosAig+JgGvKjwoAiw4LtgqPCgCLDguAaoqOioBLDgs1yo8KAIsOC7UFBD9Ng4WFg4Cyg8WASo6LAIoPiZKLBz87h0qKh0DEh0qBx0qKjosAigfHSoqOiwCKPUeKio8KAIsuh0qKjosAij1HioqPCgCLPIeKio8KAIsuh4qKjwoAizyHioqPCgCLP5w1h0qAi4b1h0qAi4Bxx4qKjwoAizPjw4WFg6PDhYWpR4qKjwoAiwBgvymHSoqHQNaHSoqAAIAAP/4AjsDLwAWAC8AJEAhAAMAA28AAAEAbwABAgIBVAABAQJYAAIBAkwcFBoZBAUYKyU0JyIvAS4BJyYiBw4CDwEGFRQWMjYlFA4BJic0NzY/AT4BNz4BHgEXHgMXFgEeCwEIDgYQBAIUAQQQDAgJCyo6LAEcpu6mAS0EHzgZPg8FHB4cBBA+MEADLc8UEwwVCSAMCQkNHhQLDBMUHSoqZXemAqp1UUgFLlQmejQQFAIQEjR6TFwFRwAAAgAA/7EEdwMLAAUAHwBLQEgYCwIEBRcSEAMDBBEBAgMDRwABBQFvAAUEBW8ABAMEbwADAgNvBgECAAACUgYBAgIAVgAAAgBKAAAdGxUUDg0ABQAFEREHBRYrBRUhETMRARUUBi8BAQYiLwEHJwE2Mh8BAScmNjsBMhYEd/uJRwPoFApE/p8GDgaC6GsBRwUOBoIBA0MJCA3zBwoHSANa/O4CuPIMCglE/p8GBoLpbAFGBgaCAQNECBYKAAAD//wAAARHAmoAEQAvAFoAZkBjBAEKAgFHAAMKCQoDCW0ABwkFCQcFbQwBBAsBAgoEAmAACgAJBwoJYAgBBQAABVQIAQUFAFgGDgENBAAFAEwUEgEAV1VOTUlIREI/Pjo5NTQsKiYlIB8bGhIvFC8AEQERDwUUKzciJjcRBwYuATY/ATYWFxEUBikBIiY/ATY0JiIGFRQGIiY1NDc2MhYUDwEzMhYOAQEWFRQOASY3NDYyFgcUFjI2NCYHIiY0NjcyPgEmJyIHDgEuATc2MzIWBxSdFSABHRQqEg4UZxwwASABwP78IR4Z0RQoOioeKiA0MpJlM3iHFSACHAGHN2SKZgEgKCIBJjYmJhsVICAVEBYCGg4ZCgoqJBALKlY7VAFZIBUBTA8KDigqCDMOIhr+YBUgQBnRFDsoJx8WHh4WSDMyZZAzeB4qIAElM0lGYgJmRBUgIBUbJiY2KAEeLBwCFiIUAhYSDhYoE05WOi4AAAUAAP/5A+QDCwAGAA8AOQA+AEgBB0AVQD47EAMCAQcABDQBAQACR0EBBAFGS7AKUFhAMAAHAwQDBwRtAAAEAQEAZQADAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTBtLsAtQWEApAAAEAQEAZQcBAwAEAAMEYAgBAQAGBQEGXwAFAgIFVAAFBQJYAAIFAkwbS7AXUFhAMAAHAwQDBwRtAAAEAQEAZQADAAQAAwRgCAEBAAYFAQZfAAUCAgVUAAUFAlgAAgUCTBtAMQAHAwQDBwRtAAAEAQQAAW0AAwAEAAMEYAgBAQAGBQEGXwAFAgIFVAAFBQJYAAIFAkxZWVlAFgAAREM9PDEuKSYeGxYTAAYABhQJBRUrJTcnBxUzFQEmDwEGFj8BNhMVFAYjISImNRE0NjchMhceAQ8BBicmIyEiBgcRFBYXITI2PQE0PwE2FgMXASM1AQcnNzYyHwEWFAHwQFVANQEVCQnECRIJxAkkXkP+MENeXkMB0CMeCQMHGwgKDQz+MCU0ATYkAdAlNAUkCBg3of6JoQJvM6EzECwQVRC9QVVBHzYBkgkJxAkSCcQJ/r5qQ15eQwHQQl4BDgQTBhwIBAM0Jf4wJTQBNiRGBwUkCAgBj6D+iaABLjShNA8PVRAsAAIAAP/5AoMDCwAHAB8AKkAnBQMCAAECAQACbQACAm4ABAEBBFQABAQBWAABBAFMIxMlNhMQBgUaKxMhNTQmDgEXBREUBgchIiYnETQ2FzM1NDYyFgcVMzIWswEdVHZUAQHQIBb96RceASAWEZTMlgISFx4BpWw7VAJQPaH+vhYeASAVAUIWIAFsZpSUZmweAAEAAP/5A6EDDAAlADBALQQBAgEAAQIAbQAAAwEAA2sAAwNuAAUBAQVUAAUFAVgAAQUBTBMlNSMVJAYFGisBFRQGByMiJj0BNCYOAQcVMzIWFxEUBgchIiYnETQ2FyE1ND4BFgOhFg4kDhZSeFIBNRceASAW/ekXHgEgFgF3ktCQAhGPDxQBFg6PO1QCUD1sHhf+vhYeASAVAUIWIAFsZ5IClgAABgAA/7EDEgMLAA8AHwAvADsAQwBnAGRAYVdFAgYIKSEZEQkBBgABAkcFAwIBBgAGAQBtBAICAAcGAAdrAA4ACQgOCWAPDQIIDAoCBgEIBl4ABwsLB1QABwcLWAALBwtMZWRhXltZU1JPTElHQT8UJBQmJiYmJiMQBR0rAREUBisBIiY1ETQ2OwEyFhcRFAYrASImNRE0NjsBMhYXERQGKwEiJjURNDY7ATIWExEhERQeATMhMj4BATMnJicjBgcFFRQGKwERFAYjISImJxEjIiY9ATQ2OwE3PgE3MzIWHwEzMhYBHgoIJAgKCggkCAqPCggkCAoKCCQICo4KByQICgoIJAcKSP4MCAgCAdACCAj+ifobBAWxBgQB6woINjQl/jAlNAE1CAoKCKwnCSwWshcqCSetCAoBt/6/CAoKCAFBCAoKCP6/CAoKCAFBCAoKCP6/CAoKCAFBCAoK/mQCEf3vDBQKChQCZUEFAQEFUyQICv3vLkRCLgITCggkCApdFRwBHhRdCgABAAAAAQAAO3t8yl8PPPUACwPoAAAAANgbenoAAAAA2Bt6ev/8/2kEdwNSAAAACAACAAAAAAAAAAEAAANS/2oAAAR2//z//wR3AAEAAAAAAAAAAAAAAAAAAAAWA+gAAAOp//8CggAAAoIAAAOqAAAD3gAAA94AAAPoAAAD3gAAA94AAANIAAAD6AAAA+gAAAIwAAAD6AAAAjsAAAR2AAAER//8A+gAAAKCAAADoAAAAxEAAAAAAAAAuAEYAXgB1gJGAswDQAPeBJIE0AUSBUIF8Ab6B1gHtghuCWQJrAn+CsIAAAABAAAAFgCGAA0AAAAAAAIANABEAHMAAACSC3AAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEACAA1AAEAAAAAAAIABwA9AAEAAAAAAAMACABEAAEAAAAAAAQACABMAAEAAAAAAAUACwBUAAEAAAAAAAYACABfAAEAAAAAAAoAKwBnAAEAAAAAAAsAEwCSAAMAAQQJAAAAagClAAMAAQQJAAEAEAEPAAMAAQQJAAIADgEfAAMAAQQJAAMAEAEtAAMAAQQJAAQAEAE9AAMAAQQJAAUAFgFNAAMAAQQJAAYAEAFjAAMAAQQJAAoAVgFzAAMAAQQJAAsAJgHJQ29weXJpZ2h0IChDKSAyMDE4IGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21mb250ZWxsb1JlZ3VsYXJmb250ZWxsb2ZvbnRlbGxvVmVyc2lvbiAxLjBmb250ZWxsb0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABDACkAIAAyADAAMQA4ACAAYgB5ACAAbwByAGkAZwBpAG4AYQBsACAAYQB1AHQAaABvAHIAcwAgAEAAIABmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQBmAG8AbgB0AGUAbABsAG8AUgBlAGcAdQBsAGEAcgBmAG8AbgB0AGUAbABsAG8AZgBvAG4AdABlAGwAbABvAFYAZQByAHMAaQBvAG4AIAAxAC4AMABmAG8AbgB0AGUAbABsAG8ARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXAAxoZWxwLWNpcmNsZWQPYW5nbGUtZG91YmxlLXVwEWFuZ2xlLWRvdWJsZS1kb3duBG1lbnUKYmF0dGVyeS0yNQpiYXR0ZXJ5LTUwA2NvZwpiYXR0ZXJ5LTc1C2JhdHRlcnktMTAwDmNhbmNlbC1jaXJjbGVkBnZvbHVtZQRwbHVzCWhvdXJnbGFzcwRjYWxjBHRpbnQKY2hhcnQtbGluZQxzb3J0LW51bWVyaWMEZWRpdARsb2NrCWxvY2stb3Blbgt0cmFzaC1lbXB0eQAAAAAAAAEAAf//AA8AAAAAAAAAAAAAAAAAAAAAABgAGAAYABgDUv9pA1L/abAALCCwAFVYRVkgIEu4AA5RS7AGU1pYsDQbsChZYGYgilVYsAIlYbkIAAgAY2MjYhshIbAAWbAAQyNEsgABAENgQi2wASywIGBmLbACLCBkILDAULAEJlqyKAEKQ0VjRVJbWCEjIRuKWCCwUFBYIbBAWRsgsDhQWCGwOFlZILEBCkNFY0VhZLAoUFghsQEKQ0VjRSCwMFBYIbAwWRsgsMBQWCBmIIqKYSCwClBYYBsgsCBQWCGwCmAbILA2UFghsDZgG2BZWVkbsAErWVkjsABQWGVZWS2wAywgRSCwBCVhZCCwBUNQWLAFI0KwBiNCGyEhWbABYC2wBCwjISMhIGSxBWJCILAGI0KxAQpDRWOxAQpDsAFgRWOwAyohILAGQyCKIIqwASuxMAUlsAQmUVhgUBthUllYI1khILBAU1iwASsbIbBAWSOwAFBYZVktsAUssAdDK7IAAgBDYEItsAYssAcjQiMgsAAjQmGwAmJmsAFjsAFgsAUqLbAHLCAgRSCwC0NjuAQAYiCwAFBYsEBgWWawAWNgRLABYC2wCCyyBwsAQ0VCKiGyAAEAQ2BCLbAJLLAAQyNEsgABAENgQi2wCiwgIEUgsAErI7AAQ7AEJWAgRYojYSBkILAgUFghsAAbsDBQWLAgG7BAWVkjsABQWGVZsAMlI2FERLABYC2wCywgIEUgsAErI7AAQ7AEJWAgRYojYSBksCRQWLAAG7BAWSOwAFBYZVmwAyUjYUREsAFgLbAMLCCwACNCsgsKA0VYIRsjIVkqIS2wDSyxAgJFsGRhRC2wDiywAWAgILAMQ0qwAFBYILAMI0JZsA1DSrAAUlggsA0jQlktsA8sILAQYmawAWMguAQAY4ojYbAOQ2AgimAgsA4jQiMtsBAsS1RYsQRkRFkksA1lI3gtsBEsS1FYS1NYsQRkRFkbIVkksBNlI3gtsBIssQAPQ1VYsQ8PQ7ABYUKwDytZsABDsAIlQrEMAiVCsQ0CJUKwARYjILADJVBYsQEAQ2CwBCVCioogiiNhsA4qISOwAWEgiiNhsA4qIRuxAQBDYLACJUKwAiVhsA4qIVmwDENHsA1DR2CwAmIgsABQWLBAYFlmsAFjILALQ2O4BABiILAAUFiwQGBZZrABY2CxAAATI0SwAUOwAD6yAQEBQ2BCLbATLACxAAJFVFiwDyNCIEWwCyNCsAojsAFgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAULLEAEystsBUssQETKy2wFiyxAhMrLbAXLLEDEystsBgssQQTKy2wGSyxBRMrLbAaLLEGEystsBsssQcTKy2wHCyxCBMrLbAdLLEJEystsB4sALANK7EAAkVUWLAPI0IgRbALI0KwCiOwAWBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsB8ssQAeKy2wICyxAR4rLbAhLLECHistsCIssQMeKy2wIyyxBB4rLbAkLLEFHistsCUssQYeKy2wJiyxBx4rLbAnLLEIHistsCgssQkeKy2wKSwgPLABYC2wKiwgYLAQYCBDI7ABYEOwAiVhsAFgsCkqIS2wKyywKiuwKiotsCwsICBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4IyCKVVggRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOBshWS2wLSwAsQACRVRYsAEWsCwqsAEVMBsiWS2wLiwAsA0rsQACRVRYsAEWsCwqsAEVMBsiWS2wLywgNbABYC2wMCwAsAFFY7gEAGIgsABQWLBAYFlmsAFjsAErsAtDY7gEAGIgsABQWLBAYFlmsAFjsAErsAAWtAAAAAAARD4jOLEvARUqLbAxLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2E4LbAyLC4XPC2wMywgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhsAFDYzgtsDQssQIAFiUgLiBHsAAjQrACJUmKikcjRyNhIFhiGyFZsAEjQrIzAQEVFCotsDUssAAWsAQlsAQlRyNHI2GwCUMrZYouIyAgPIo4LbA2LLAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjILAIQyCKI0cjRyNhI0ZgsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhIyAgsAQmI0ZhOBsjsAhDRrACJbAIQ0cjRyNhYCCwBEOwAmIgsABQWLBAYFlmsAFjYCMgsAErI7AEQ2CwASuwBSVhsAUlsAJiILAAUFiwQGBZZrABY7AEJmEgsAQlYGQjsAMlYGRQWCEbIyFZIyAgsAQmI0ZhOFktsDcssAAWICAgsAUmIC5HI0cjYSM8OC2wOCywABYgsAgjQiAgIEYjR7ABKyNhOC2wOSywABawAyWwAiVHI0cjYbAAVFguIDwjIRuwAiWwAiVHI0cjYSCwBSWwBCVHI0cjYbAGJbAFJUmwAiVhuQgACABjYyMgWGIbIVljuAQAYiCwAFBYsEBgWWawAWNgIy4jICA8ijgjIVktsDossAAWILAIQyAuRyNHI2EgYLAgYGawAmIgsABQWLBAYFlmsAFjIyAgPIo4LbA7LCMgLkawAiVGUlggPFkusSsBFCstsDwsIyAuRrACJUZQWCA8WS6xKwEUKy2wPSwjIC5GsAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xKwEUKy2wPiywNSsjIC5GsAIlRlJYIDxZLrErARQrLbA/LLA2K4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrErARQrsARDLrArKy2wQCywABawBCWwBCYgLkcjRyNhsAlDKyMgPCAuIzixKwEUKy2wQSyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbErARQrLbBCLLA1Ky6xKwEUKy2wQyywNishIyAgPLAEI0IjOLErARQrsARDLrArKy2wRCywABUgR7AAI0KyAAEBFRQTLrAxKi2wRSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKyAAEAKrEABUKzCgIBCCqxAAVCsw4AAQgqsQAGQroCwAABAAkqsQAHQroAQAABAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbMMAgEMKrgB/4WwBI2xAgBEAAA=') format('truetype'); } /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ @@ -17,7 +17,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?37278242#fontello') format('svg'); + src: url('../font/fontello.svg?73205958#fontello') format('svg'); } } */ diff --git a/static/glyphs/css/fontello.css b/static/glyphs/css/fontello.css index 777b4dc367b..6548310e8e0 100755 --- a/static/glyphs/css/fontello.css +++ b/static/glyphs/css/fontello.css @@ -1,11 +1,11 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?25902905'); - src: url('../font/fontello.eot?25902905#iefix') format('embedded-opentype'), - url('../font/fontello.woff2?25902905') format('woff2'), - url('../font/fontello.woff?25902905') format('woff'), - url('../font/fontello.ttf?25902905') format('truetype'), - url('../font/fontello.svg?25902905#fontello') format('svg'); + src: url('../font/fontello.eot?50735338'); + src: url('../font/fontello.eot?50735338#iefix') format('embedded-opentype'), + url('../font/fontello.woff2?50735338') format('woff2'), + url('../font/fontello.woff?50735338') format('woff'), + url('../font/fontello.ttf?50735338') format('truetype'), + url('../font/fontello.svg?50735338#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -15,7 +15,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?25902905#fontello') format('svg'); + src: url('../font/fontello.svg?50735338#fontello') format('svg'); } } */ diff --git a/static/glyphs/demo.html b/static/glyphs/demo.html index eedc1ca63f0..0ec1b165923 100755 --- a/static/glyphs/demo.html +++ b/static/glyphs/demo.html @@ -229,11 +229,11 @@ } @font-face { font-family: 'fontello'; - src: url('./font/fontello.eot?94753240'); - src: url('./font/fontello.eot?94753240#iefix') format('embedded-opentype'), - url('./font/fontello.woff?94753240') format('woff'), - url('./font/fontello.ttf?94753240') format('truetype'), - url('./font/fontello.svg?94753240#fontello') format('svg'); + src: url('./font/fontello.eot?92660326'); + src: url('./font/fontello.eot?92660326#iefix') format('embedded-opentype'), + url('./font/fontello.woff?92660326') format('woff'), + url('./font/fontello.ttf?92660326') format('truetype'), + url('./font/fontello.svg?92660326#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -275,7 +275,7 @@ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } - +
-

- fontello - font demo -

+

fontello font demo

-
+
-
icon-help-circled0xe800
-
icon-angle-double-up0xe801
-
icon-angle-double-down0xe802
-
icon-menu0xe803
+
icon-help-circled0xe800
+
icon-angle-double-up0xe801
+
icon-angle-double-down0xe802
+
icon-menu0xe803
-
icon-battery-250xe804
-
icon-battery-500xe805
-
icon-cog0xe806
-
icon-battery-750xe807
+
icon-battery-250xe804
+
icon-battery-500xe805
+
icon-cog0xe806
+
icon-battery-750xe807
-
icon-battery-1000xe808
-
icon-cancel-circled0xe809
-
icon-volume0xe80a
-
icon-plus0xe80b
+
icon-battery-1000xe808
+
icon-cancel-circled0xe809
+
icon-volume0xe80a
+
icon-plus0xe80b
-
icon-hourglass0xe80c
-
icon-calc0xe80d
-
icon-tint0xe80e
-
icon-chart-line0xe80f
+
icon-hourglass0xe80c
+
icon-calc0xe80d
+
icon-tint0xe80e
+
icon-chart-line0xe80f
-
icon-sort-numeric0xe810
-
icon-edit0xe811
-
icon-lock0xe812
-
icon-lock-open0xe813
+
icon-sort-numeric0xe810
+
icon-edit0xe811
+
icon-lock0xe812
+
icon-lock-open0xe813
-
icon-trash-empty0xe814
+
icon-trash-empty0xe814
- + \ No newline at end of file diff --git a/static/glyphs/font/fontello.eot b/static/glyphs/font/fontello.eot index 8d9c020d8a1..610cc3bdf2b 100755 Binary files a/static/glyphs/font/fontello.eot and b/static/glyphs/font/fontello.eot differ diff --git a/static/glyphs/font/fontello.svg b/static/glyphs/font/fontello.svg index 49e6adb764e..6d0b3f8a188 100755 --- a/static/glyphs/font/fontello.svg +++ b/static/glyphs/font/fontello.svg @@ -1,7 +1,7 @@ -Copyright (C) 2016 by original authors @ fontello.com +Copyright (C) 2018 by original authors @ fontello.com diff --git a/static/glyphs/font/fontello.ttf b/static/glyphs/font/fontello.ttf index 22bb5572afc..98628181089 100755 Binary files a/static/glyphs/font/fontello.ttf and b/static/glyphs/font/fontello.ttf differ diff --git a/static/glyphs/font/fontello.woff b/static/glyphs/font/fontello.woff index 26db5a25515..a14e2921e24 100755 Binary files a/static/glyphs/font/fontello.woff and b/static/glyphs/font/fontello.woff differ diff --git a/static/glyphs/font/fontello.woff2 b/static/glyphs/font/fontello.woff2 index 98049a0888b..bf53e9f8550 100755 Binary files a/static/glyphs/font/fontello.woff2 and b/static/glyphs/font/fontello.woff2 differ diff --git a/static/images/TripleDown.svg b/static/images/TripleDown.svg new file mode 100644 index 00000000000..2fb7ec80baf --- /dev/null +++ b/static/images/TripleDown.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/static/images/TripleUp.svg b/static/images/TripleUp.svg new file mode 100644 index 00000000000..a773f61fd7e --- /dev/null +++ b/static/images/TripleUp.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/static/images/browserstack-logo.png b/static/images/browserstack-logo.png new file mode 100644 index 00000000000..765fea8fe1f Binary files /dev/null and b/static/images/browserstack-logo.png differ diff --git a/static/profile/js/profileeditor.js b/static/profile/js/profileeditor.js index 94a8671e40f..c1443e5cade 100644 --- a/static/profile/js/profileeditor.js +++ b/static/profile/js/profileeditor.js @@ -18,6 +18,10 @@ client.init(function loaded () { + if (c_profile !== null) { + return; // already loaded so don't load again + } + var translate = client.translate; var defaultprofile = { @@ -630,11 +634,11 @@ } function toTimeString(minfrommidnight) { - return moment().startOf('day').add(minfrommidnight,'minutes').format('HH:mm'); + return moment.utc().startOf('day').add(minfrommidnight,'minutes').format('HH:mm'); // using utc to avoid daylight saving offset } function toDisplayTime (minfrommidnight) { - var time = moment().startOf('day').add(minfrommidnight,'minutes'); + var time = moment.utc().startOf('day').add(minfrommidnight,'minutes'); // using utc to avoid daylight saving offset return client.settings.timeFormat === 24 ? time.format('HH:mm') : time.format('h:mm A'); } @@ -667,18 +671,17 @@ adjustedRecord.defaultProfile = currentprofile; adjustedRecord.units = client.settings.units; - if (record.convertedOnTheFly) { - var result = window.confirm(translate('Profile is going to be saved in newer format used in Nightscout 0.9.0 and above and will not be usable in older versions anymore.\nAre you sure?')); - if (!result) { - return; - } - } - delete record.convertedOnTheFly; delete adjustedRecord.convertedOnTheFly; console.info('saving profile'); + peStatus.hide().text(translate('Saving profile')).fadeIn('slow'); + // Hide the form until the ajax PUT is done. + // This is a crude way of preventing the user from changing the inputs whilst waiting. + // If the user was able to make changes, they'd be lost when the done callback redraws anyway. + $('#pe_form').hide(); + $.ajax({ method: 'PUT' , url: '/api/v1/profile/' @@ -686,12 +689,14 @@ , headers: client.headers() }).done(function postSuccess (data, status) { console.info('profile saved', data); + $('#pe_form').show(); // allow edits again peStatus.hide().text(status).fadeIn('slow'); record._id = data._id; initRecord(); dirty = false; }).fail(function(xhr, status, errorThrown) { console.error('Profile not saved', status, errorThrown); + $('#pe_form').show(); // allow edits again peStatus.hide().text(status).fadeIn('slow'); }); return false; diff --git a/static/report/js/loopalyzer.js b/static/report/js/loopalyzer.js new file mode 100644 index 00000000000..ebe434f2e64 --- /dev/null +++ b/static/report/js/loopalyzer.js @@ -0,0 +1,52 @@ +// Moves backward one day +function loopalyzerBackward() { + var moment = window.moment; + var from = moment($("#rp_from").val()).subtract(1,'day'); + var to = moment($("#rp_to").val()).subtract(1,'day'); + $("#rp_from").val(from.format('YYYY-MM-DD')); + $("#rp_to").val(to.format('YYYY-MM-DD')); + $("#rp_show").click(); +} + +// Moves backward same amount as shown (e.g. whole week) +function loopalyzerMoreBackward() { + var moment = window.moment; + var from = moment($("#rp_from").val()) + var to = moment($("#rp_to").val()) + var diff = to.diff(from, 'days') + 1; + from.subtract(diff, 'days'); + to.subtract(diff, 'days'); + $("#rp_from").val(from.format('YYYY-MM-DD')); + $("#rp_to").val(to.format('YYYY-MM-DD')); + $("#rp_show").click(); +} + +// Moves forward one day +function loopalyzerForward() { + var moment = window.moment; + var from = moment($("#rp_from").val()).add(1,'day'); + var to = moment($("#rp_to").val()).add(1,'day'); + if (to <= moment()) { + $("#rp_from").val(from.format('YYYY-MM-DD')); + $("#rp_to").val(to.format('YYYY-MM-DD')); + $("#rp_show").click(); + } +} + +// Moves forward same amount as shown (e.g. whole week) +function loopalyzerMoreForward() { + var moment = window.moment; + var from = moment($("#rp_from").val()) + var to = moment($("#rp_to").val()) + var diff = to.diff(from, 'days') + 1; + from.add(diff, 'days'); + to.add(diff, 'days'); + if (to > moment()) { + to = moment(); + from = moment(); + from.subtract(diff-1, 'days'); + } + $("#rp_from").val(from.format('YYYY-MM-DD')); + $("#rp_to").val(to.format('YYYY-MM-DD')); + $("#rp_show").click(); +} diff --git a/static/report/js/predictions.js b/static/report/js/predictions.js new file mode 100644 index 00000000000..336483a0dce --- /dev/null +++ b/static/report/js/predictions.js @@ -0,0 +1,40 @@ + +var predictedOffset = 0; + +function predictForward() { + predictedOffset += 5; + $("#rp_predictedOffset").html(predictedOffset); + $("#rp_show").click(); +} + +function predictMoreForward() { + predictedOffset += 30; + $("#rp_predictedOffset").html(predictedOffset); + $("#rp_show").click(); +} + +function predictBackward() { + predictedOffset -= 5; + $("#rp_predictedOffset").html(predictedOffset); + $("#rp_show").click(); +} + +function predictMoreBackward() { + predictedOffset -= 30; + $("#rp_predictedOffset").html(predictedOffset); + $("#rp_show").click(); +} + +function predictResetToZero() { + predictedOffset = 0; + $("#rp_predictedOffset").html(predictedOffset); + $("#rp_show").click(); +} + +$(document).on('change', '#rp_optionspredicted', function() { + if (this.checked) + $("#rp_predictedSettings").show(); + else + $("#rp_predictedSettings").hide(); + predictResetToZero(); +}); diff --git a/static/report/js/report.js b/static/report/js/report.js index ba637365dcb..f7b02ec44af 100644 --- a/static/report/js/report.js +++ b/static/report/js/report.js @@ -170,8 +170,10 @@ if (client.settings.scaleY === 'linear') { $('#rp_linear').prop('checked', true); + $('#wrp_linear').prop('checked', true); } else { $('#rp_log').prop('checked', true); + $('#wrp_log').prop('checked', true); } $('.menutab').click(switchreport_handler); @@ -195,6 +197,8 @@ var options = { width: 1000 , height: 300 + , weekwidth: 1000 + , weekheight: 300 , targetLow: 3.5 , targetHigh: 10 , raw: true @@ -206,6 +210,7 @@ , cob : true , basal : true , scale: report_plugins.consts.scaleYFromSettings(client) + , weekscale: report_plugins.consts.scaleYFromSettings(client) , units: client.settings.units }; @@ -219,6 +224,8 @@ options.iob = $('#rp_optionsiob').is(':checked'); options.cob = $('#rp_optionscob').is(':checked'); options.openAps = $('#rp_optionsopenaps').is(':checked'); + options.predicted = $('#rp_optionspredicted').is(':checked'); + options.predictedTruncate = $('#rp_optionsPredictedTruncate').is(':checked'); options.basal = $('#rp_optionsbasal').is(':checked'); options.notes = $('#rp_optionsnotes').is(':checked'); options.food = $('#rp_optionsfood').is(':checked'); @@ -226,9 +233,18 @@ options.insulindistribution = $('#rp_optionsdistribution').is(':checked'); options.carbs = $('#rp_optionscarbs').is(':checked'); options.scale = ( $('#rp_linear').is(':checked') ? report_plugins.consts.SCALE_LINEAR : report_plugins.consts.SCALE_LOG ); + options.weekscale = ( $('#wrp_linear').is(':checked') ? report_plugins.consts.SCALE_LINEAR : report_plugins.consts.SCALE_LOG ); options.order = ( $('#rp_oldestontop').is(':checked') ? report_plugins.consts.ORDER_OLDESTONTOP : report_plugins.consts.ORDER_NEWESTONTOP ); options.width = parseInt($('#rp_size :selected').attr('x')); + options.weekwidth = parseInt($('#wrp_size :selected').attr('x')); options.height = parseInt($('#rp_size :selected').attr('y')); + options.weekheight = parseInt($('#wrp_size :selected').attr('y')); + options.loopalyzer = $("#loopalyzer").hasClass( "selected" ); // We only want to run through Loopalyzer if that tab is selected + if (options.loopalyzer) { + options.iob = true; + options.cob = true; + options.openAps = true; + } var matchesneeded = 0; @@ -510,6 +526,8 @@ if (plugin.name == 'daytoday' && ! $('#daytoday').hasClass('selected')) skipRender = true; if (plugin.name == 'treatments' && ! $('#treatments').hasClass('selected')) skipRender = true; + if (plugin.name == 'weektoweek' && ! $('#weektoweek').hasClass('selected')) skipRender = true; + if (plugin.name == 'loopalyzer' && ! $('#loopalyzer').hasClass('selected')) skipRender = true; if (skipRender) { console.log('Skipping ',plugin.name); @@ -545,7 +563,7 @@ function loadData(day, options, callback) { // check for loaded data - if ((options.openAps || options.iob || options.cob) && datastorage[day] && !datastorage[day].devicestatus.length) { + if ((options.openAps || options.predicted || options.iob || options.cob) && datastorage[day] && !datastorage[day].devicestatus.length) { // OpenAPS requested but data not loaded. Load anyway ... } else if (datastorage[day] && day !== moment().format('YYYY-MM-DD')) { callback(day); @@ -616,8 +634,9 @@ data.sgv.sort(function(a, b) { return a.mills - b.mills; }); var lastDate = 0; data.sgv = data.sgv.filter(function(d) { - var ok = (lastDate + ONE_MIN_IN_MS) < d.mills; + var ok = (lastDate + ONE_MIN_IN_MS) <= d.mills; lastDate = d.mills; + if (!ok) { console.log("itm",JSON.stringify(d)); } return ok; }); data.mbg = mbgData.slice(); @@ -635,6 +654,7 @@ var tquery = '?find[created_at][$gte]='+new Date(from).toISOString()+'&find[created_at][$lt]='+new Date(to).toISOString(); return $.ajax('/api/v1/treatments.json'+tquery, { headers: client.headers() + , cache: false , success: function (xhr) { treatmentData = xhr.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); @@ -665,7 +685,7 @@ data.devicestatus = []; return $.Deferred().resolve(); } - if(options.iob || options.cob || options.openAps) { + if (options.iob || options.cob || options.openAps || options.predicted) { $('#info-' + day).html(''+translate('Loading device status data of')+' '+day+' ...'); var tquery = '?find[created_at][$gte]=' + new Date(from).toISOString() + '&find[created_at][$lt]=' + new Date(to).toISOString() + '&count=10000'; return $.ajax('/api/v1/devicestatus.json'+tquery, { @@ -733,6 +753,9 @@ } // treatments data.dailyCarbs = 0; + data.dailyProtein = 0; + data.dailyFat = 0; + data.treatments.forEach(function (d) { if (parseFloat(d.insulin) > maxInsulinValue) { maxInsulinValue = parseFloat(d.insulin); @@ -741,7 +764,13 @@ maxCarbsValue = parseFloat(d.carbs); } if (d.carbs) { - data.dailyCarbs += d.carbs; + data.dailyCarbs += Number(d.carbs); + } + if (d.protein) { + data.dailyProtein += Number(d.protein); + } + if (d.fat) { + data.dailyFat += Number(d.fat); } }); if (data.dailyCarbs > maxDailyCarbsValue) { diff --git a/static/robots.txt b/static/robots.txt index 1f53798bb4f..68ad19bb9f2 100644 --- a/static/robots.txt +++ b/static/robots.txt @@ -1,2 +1,2 @@ -User-agent: * -Disallow: / +User-agent: Browsershots +Disallow: diff --git a/swagger.json b/swagger.json old mode 100644 new mode 100755 index 28ca65fc6d9..dce7854e05b --- a/swagger.json +++ b/swagger.json @@ -8,7 +8,7 @@ "info": { "title": "Nightscout API", "description": "Own your DData with the Nightscout API", - "version": "0.10.3-master-20180805", + "version": "13.0.1", "license": { "name": "AGPL 3", "url": "https://www.gnu.org/licenses/agpl.txt" @@ -577,6 +577,81 @@ "description": "Treatments to be uploaded.", "required": true } + }, + "delete": { + "tags": [ + "Treatments" + ], + "summary": "Delete treatments matching query.", + "description": "Remove treatments, same search syntax as GET.", + "operationId": "remove", + "parameters": [ + { + "name": "find", + "in": "query", + "description": "The query used to find treatments to delete, support nested query syntax, for example `find[insulin][$gte]=3`. All find parameters are interpreted as strings.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "count", + "in": "query", + "description": "Number of entries to return.", + "required": false, + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "Empty list is success." + } + } + } + }, + "/treatments/{spec}": { + "delete": { + "summary": "Delete treatments record with id provided in spec", + "description": "The Treatments endpoint returns information about the\nNightscout devicestatus records.\n", + "parameters": [ + { + "name": "spec", + "in": "path", + "description": "treatment id, such as `55cf81bc436037528ec75fa5`\n", + "required": true, + "schema": { + "type": "string" + } + } + ], + "tags": [ + "Treatments" + ], + "responses": { + "200": { + "description": "A status record of the delete.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteStatus" + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } } }, "/profile": { @@ -640,6 +715,166 @@ } } } + }, + "/devicestatus/": { + "get": { + "summary": "All Devicestatuses matching query", + "description": "The Devicestatus endpoint returns information about the Nightscout devicestatus records.", + "parameters": [ + { + "name": "find", + "in": "query", + "description": "The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "count", + "in": "query", + "description": "Number of devicestatus records to return.", + "required": false, + "schema": { + "type": "number" + } + } + ], + "tags": [ + "Devicestatus" + ], + "responses": { + "200": { + "description": "An array of devicestatus entries", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Devicestatuses" + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Devicestatus" + ], + "summary": "Add new devicestatus records.", + "description": "", + "operationId": "addDevicestatuses", + "responses": { + "200": { + "description": "Rejected list of device statuses. Empty list is success." + }, + "405": { + "description": "Invalid input" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Devicestatuses" + } + } + }, + "description": "Device statuses to be uploaded.", + "required": true + } + }, + "delete": { + "summary": "Delete all Devicestatus records matching query", + "description": "The Devicestatus endpoint returns information about the\nNightscout devicestatus records.\n", + "parameters": [ + { + "name": "find", + "in": "query", + "description": "The query used to find entries, support nested query syntax, for\nexample `find[created_at][$gte]=2015-08-27`. All find parameters\nare interpreted as strings.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": [ + "Devicestatus" + ], + "responses": { + "200": { + "description": "A status record of the delete.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteStatus" + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/devicestatus/{spec}": { + "delete": { + "summary": "Delete devicestatus record with id provided in spec", + "description": "The Devicestatus endpoint returns information about the\nNightscout devicestatus records.\n", + "parameters": [ + { + "name": "spec", + "in": "path", + "description": "entry id, such as `55cf81bc436037528ec75fa5`\n", + "required": true, + "schema": { + "type": "string" + } + } + ], + "tags": [ + "Devicestatus" + ], + "responses": { + "200": { + "description": "A status record of the delete.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteStatus" + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } } }, "components": { @@ -672,7 +907,7 @@ }, "dateString": { "type": "string", - "description": "dateString, prefer ISO `8601`" + "description": "dateString, MUST be ISO `8601` format date parseable by Javascript Date()" }, "date": { "type": "number", @@ -710,6 +945,211 @@ "$ref": "#/components/schemas/Entry" } }, + "Devicestatus": { + "required": [ + "device", + "created_at" + ], + "properties": { + "device": { + "type": "string", + "description": "Device type and hostname for example openaps://hostname" + }, + "created_at": { + "type": "string", + "description": "dateString, prefer ISO `8601`" + }, + "openaps": { + "type": "string", + "description": "OpenAPS devicestatus record - TODO: Fill Out Details" + }, + "loop": { + "type": "string", + "description": "Loop devicestatus record - TODO: Fill Out Details" + }, + "pump": { + "$ref": "#/components/schemas/pump" + }, + "uploader": { + "$ref": "#/components/schemas/uploader" + }, + "xdripjs": { + "$ref": "#/components/schemas/xdripjs" + } + } + }, + "Devicestatuses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Devicestatus" + } + }, + "pump": { + "properties": { + "clock": { + "type": "string", + "description": "dateString, prefer ISO `8601`" + }, + "battery": { + "$ref": "#/components/schemas/pumpbattery" + }, + "reservoir": { + "type": "number", + "description": "Amount of insulin remaining in pump reservoir" + }, + "status": { + "$ref": "#/components/schemas/pumpstatus" + } + } + }, + "pumpbattery": { + "properties": { + "status": { + "type": "string", + "description": "Pump Battery Status String" + }, + "voltage": { + "type": "number", + "description": "Pump Battery Voltage Level" + } + } + }, + "pumpstatus": { + "properties": { + "status": { + "type": "string", + "description": "Pump Status String" + }, + "bolusing": { + "type": "boolean", + "description": "Is Pump Bolusing" + }, + "suspended": { + "type": "boolean", + "description": "Is Pump Suspended" + }, + "timestamp": { + "type": "string", + "description": "dateString, prefer ISO `8601`" + } + } + }, + "uploader": { + "properties": { + "batteryVoltage": { + "type": "number", + "description": "Uploader Device Battery Voltage" + }, + "battery": { + "type": "number", + "description": "Uploader Device Battery Percentage Charge Remaining" + } + } + }, + "xdripjs": { + "properties": { + "state": { + "type": "number", + "description": "CGM Sensor Session State Code" + }, + "stateString": { + "type": "string", + "description": "CGM Sensor Session State String" + }, + "stateStringShort": { + "type": "string", + "description": "CGM Sensor Session State Short String" + }, + "txId": { + "type": "string", + "description": "CGM Transmitter ID" + }, + "txStatus": { + "type": "number", + "description": "CGM Transmitter Status" + }, + "txStatusString": { + "type": "string", + "description": "CGM Transmitter Status String" + }, + "txStatusStringShort": { + "type": "string", + "description": "CGM Transmitter Status Short String" + }, + "txActivation": { + "type": "number", + "description": "CGM Transmitter Activation Milliseconds After Epoch" + }, + "mode": { + "type": "string", + "description": "Mode xdrip-js Application Operationg: expired, not expired, etc." + }, + "timestamp": { + "type": "number", + "description": "Last Update Milliseconds After Epoch" + }, + "rssi": { + "type": "number", + "description": "Receive Signal Strength of Transmitter" + }, + "unfiltered": { + "type": "number", + "description": "Most Recent Raw Unfiltered Glucose" + }, + "filtered": { + "type": "number", + "description": "Most Recent Raw Filtered Glucose" + }, + "noise": { + "type": "number", + "description": "Calculated Noise Value - 1=Clean, 2=Light, 3=Medium, 4=Heavy" + }, + "noiseString": { + "type": "number", + "description": "Noise Value String" + }, + "slope": { + "type": "number", + "description": "Calibration Slope Value" + }, + "intercept": { + "type": "number", + "description": "Calibration Intercept Value" + }, + "calType": { + "type": "string", + "description": "Algorithm Used to Calculate Calibration Values" + }, + "lastCalibrationDate": { + "type": "number", + "description": "Most Recent Calibration Milliseconds After Epoch" + }, + "sessionStart": { + "type": "number", + "description": "Sensor Session Start Milliseconds After Epoch" + }, + "batteryTimestamp": { + "type": "number", + "description": "Most Recent Batter Status Read Milliseconds After Epoch" + }, + "voltagea": { + "type": "number", + "description": "Voltage of Battery A" + }, + "voltageb": { + "type": "number", + "description": "Voltage of Battery B" + }, + "temperature": { + "type": "number", + "description": "Transmitter Temperature" + }, + "resistance": { + "type": "number", + "description": "Sensor Resistance" + } + } + }, "Treatment": { "properties": { "_id": { @@ -734,7 +1174,15 @@ }, "carbs": { "type": "number", - "description": "Number of carbs." + "description": " Amount of carbs consumed in grams." + }, + "protein": { + "type": "number", + "description": " Amount of protein consumed in grams." + }, + "fat": { + "type": "number", + "description": " Amount of fat consumed in grams." }, "insulin": { "type": "number", @@ -946,7 +1394,49 @@ "type": "object" } } + }, + "DeleteStatus": { + "properties": { + "n": { + "type": "integer", + "format": "int32", + "description": "Number of records deleted" + }, + "optime": { + "$ref": "#/components/schemas/optime" + }, + "electionId": { + "type": "string", + "description": "Election id of operation" + }, + "ok": { + "type": "integer", + "format": "int32", + "description": "Status of whether delete was successful" + }, + "operationTime": { + "type": "string", + "description": "Time delete operation was executed" + }, + "$clusterTime": { + "type": "string", + "description": "Information about execution time in cluster environment" + } + } + }, + "optime": { + "properties": { + "ts": { + "type": "string", + "description": "Time the operation started" + }, + "t": { + "type": "integer", + "format": "int32", + "description": "Time the operation took to complete" + } + } } } } -} \ No newline at end of file +} diff --git a/swagger.yaml b/swagger.yaml old mode 100644 new mode 100755 index ac39b12f695..a08f701d7a5 --- a/swagger.yaml +++ b/swagger.yaml @@ -4,7 +4,7 @@ servers: info: title: Nightscout API description: Own your DData with the Nightscout API - version: 0.10.3-master-20180805 + version: 13.0.1 license: name: AGPL 3 url: 'https://www.gnu.org/licenses/agpl.txt' @@ -444,6 +444,60 @@ paths: $ref: '#/components/schemas/Treatments' description: Treatments to be uploaded. required: true + delete: + tags: + - Treatments + summary: Delete treatments matching query. + description: 'Remove treatments, same search syntax as GET.' + operationId: remove + parameters: + - name: find + in: query + description: >- + The query used to find treatments to delete, + support nested query syntax, for example `find[insulin][$gte]=3`. + All find parameters are interpreted as strings. + required: false + schema: + type: string + - name: count + in: query + description: Number of entries to return. + required: false + schema: + type: number + responses: + '200': + description: Empty list is success. + '/treatments/{spec}': + delete: + summary: Delete treatments record with id provided in spec + description: | + The Treatments endpoint returns information about the + Nightscout devicestatus records. + parameters: + - name: spec + in: path + description: | + treatment id, such as `55cf81bc436037528ec75fa5` + required: true + schema: + type: string + tags: + - Treatments + responses: + '200': + description: A status record of the delete. + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteStatus' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /profile: get: summary: Profile @@ -484,6 +538,120 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /devicestatus/: + get: + summary: All Devicestatuses matching query + description: >- + The Devicestatus endpoint returns information about the Nightscout + devicestatus records. + parameters: + - name: find + in: query + description: >- + The query used to find entries, support nested query syntax, for + example `find[dateString][$gte]=2015-08-27`. All find parameters + are interpreted as strings. + required: false + schema: + type: string + - name: count + in: query + description: Number of devicestatus records to return. + required: false + schema: + type: number + tags: + - Devicestatus + responses: + '200': + description: An array of devicestatus entries + content: + application/json: + schema: + $ref: '#/components/schemas/Devicestatuses' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + tags: + - Devicestatus + summary: Add new devicestatus records. + description: '' + operationId: addDevicestatuses + responses: + '200': + description: Rejected list of device statuses. Empty list is success. + '405': + description: Invalid input + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Devicestatuses' + description: Device statuses to be uploaded. + required: true + delete: + summary: Delete all Devicestatus records matching query + description: | + The Devicestatus endpoint returns information about the + Nightscout devicestatus records. + parameters: + - name: find + in: query + description: | + The query used to find entries, support nested query syntax, for + example `find[created_at][$gte]=2015-08-27`. All find parameters + are interpreted as strings. + required: false + schema: + type: string + tags: + - Devicestatus + responses: + '200': + description: A status record of the delete. + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteStatus' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '/devicestatus/{spec}': + delete: + summary: Delete devicestatus record with id provided in spec + description: | + The Devicestatus endpoint returns information about the + Nightscout devicestatus records. + parameters: + - name: spec + in: path + description: | + entry id, such as `55cf81bc436037528ec75fa5` + required: true + schema: + type: string + tags: + - Devicestatus + responses: + '200': + description: A status record of the delete. + content: + application/json: + schema: + $ref: '#/components/schemas/DeleteStatus' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' components: securitySchemes: api_secret: @@ -495,7 +663,9 @@ components: type: apiKey name: token in: query - description: Add token as query item in the URL. You can manage access Token in `/admin`. This uses json webtokens. + description: >- + Add token as query item in the URL. You can manage access Token in + `/admin`. This uses json webtokens. jwtoken: type: http scheme: bearer @@ -509,7 +679,7 @@ components: description: 'sgv, mbg, cal, etc' dateString: type: string - description: 'dateString, prefer ISO `8601`' + description: 'dateString, MUST be ISO `8601` format date parseable by Javascript Date()' date: type: number description: Epoch @@ -543,6 +713,152 @@ components: type: array items: $ref: '#/components/schemas/Entry' + Devicestatus: + required: + - device + - created_at + properties: + device: + type: string + description: 'Device type and hostname for example openaps://hostname' + created_at: + type: string + description: 'dateString, MUST be ISO `8601` format date parseable by Javascript Date()' + openaps: + type: string + description: 'OpenAPS devicestatus record - TODO: Fill Out Details' + loop: + type: string + description: 'Loop devicestatus record - TODO: Fill Out Details' + pump: + $ref: '#/components/schemas/pump' + uploader: + $ref: '#/components/schemas/uploader' + xdripjs: + $ref: '#/components/schemas/xdripjs' + Devicestatuses: + type: array + items: + $ref: '#/components/schemas/Devicestatus' + pump: + properties: + clock: + type: string + description: 'dateString, MUST be ISO `8601` format date parseable by Javascript Date()' + battery: + $ref: '#/components/schemas/pumpbattery' + reservoir: + type: number + description: Amount of insulin remaining in pump reservoir + status: + $ref: '#/components/schemas/pumpstatus' + pumpbattery: + properties: + status: + type: string + description: Pump Battery Status String + voltage: + type: number + description: Pump Battery Voltage Level + pumpstatus: + properties: + status: + type: string + description: Pump Status String + bolusing: + type: boolean + description: Is Pump Bolusing + suspended: + type: boolean + description: Is Pump Suspended + timestamp: + type: string + description: 'dateString, MUST be ISO `8601` format date parseable by Javascript Date()' + uploader: + properties: + batteryVoltage: + type: number + description: Uploader Device Battery Voltage + battery: + type: number + description: Uploader Device Battery Percentage Charge Remaining + xdripjs: + properties: + state: + type: number + description: CGM Sensor Session State Code + stateString: + type: string + description: CGM Sensor Session State String + stateStringShort: + type: string + description: CGM Sensor Session State Short String + txId: + type: string + description: CGM Transmitter ID + txStatus: + type: number + description: CGM Transmitter Status + txStatusString: + type: string + description: CGM Transmitter Status String + txStatusStringShort: + type: string + description: CGM Transmitter Status Short String + txActivation: + type: number + description: CGM Transmitter Activation Milliseconds After Epoch + mode: + type: string + description: 'Mode xdrip-js Application Operationg: expired, not expired, etc.' + timestamp: + type: number + description: Last Update Milliseconds After Epoch + rssi: + type: number + description: Receive Signal Strength of Transmitter + unfiltered: + type: number + description: Most Recent Raw Unfiltered Glucose + filtered: + type: number + description: Most Recent Raw Filtered Glucose + noise: + type: number + description: 'Calculated Noise Value - 1=Clean, 2=Light, 3=Medium, 4=Heavy' + noiseString: + type: number + description: Noise Value String + slope: + type: number + description: Calibration Slope Value + intercept: + type: number + description: Calibration Intercept Value + calType: + type: string + description: Algorithm Used to Calculate Calibration Values + lastCalibrationDate: + type: number + description: Most Recent Calibration Milliseconds After Epoch + sessionStart: + type: number + description: Sensor Session Start Milliseconds After Epoch + batteryTimestamp: + type: number + description: Most Recent Batter Status Read Milliseconds After Epoch + voltagea: + type: number + description: Voltage of Battery A + voltageb: + type: number + description: Voltage of Battery B + temperature: + type: number + description: Transmitter Temperature + resistance: + type: number + description: Sensor Resistance Treatment: properties: _id: @@ -562,7 +878,13 @@ components: description: 'Method used to obtain glucose, Finger or Sensor.' carbs: type: number - description: Number of carbs. + description: Amount of carbs consumed in grams. + protein: + type: number + description: Amount of protein consumed in grams. + fat: + type: number + description: Amount of fat consumed in grams. insulin: type: number description: 'Amount of insulin, if any.' @@ -723,4 +1045,34 @@ components: message: type: string fields: - type: object \ No newline at end of file + type: object + DeleteStatus: + properties: + 'n': + type: integer + format: int32 + description: Number of records deleted + optime: + $ref: '#/components/schemas/optime' + electionId: + type: string + description: Election id of operation + ok: + type: integer + format: int32 + description: Status of whether delete was successful + operationTime: + type: string + description: Time delete operation was executed + $clusterTime: + type: string + description: Information about execution time in cluster environment + optime: + properties: + ts: + type: string + description: Time the operation started + t: + type: integer + format: int32 + description: Time the operation took to complete diff --git a/tests/admintools.test.js b/tests/admintools.test.js index b5be4858edb..0b95acdf6c1 100644 --- a/tests/admintools.test.js +++ b/tests/admintools.test.js @@ -29,6 +29,9 @@ var someData = { 'created_at': '2025-09-28T16:41:07.144Z' } ], + '/api/v1/devicestatus/?find[created_at][$lte]=': { + n: 1 + }, '/api/v1/treatments.json?&find[created_at][$gte]=': [ { '_id': '5609a9203c8104a8195b1c1e', @@ -38,6 +41,9 @@ var someData = { 'created_at': '2025-09-28T20:54:00.000Z' } ], + '/api/v1/treatments/?find[created_at][$lte]=': { + n: 1 + }, '/api/v1/entries.json?&find[date][$gte]=': [ { '_id': '560983f326c5a592d9b9ae0c', @@ -51,8 +57,11 @@ var someData = { 'rssi': 178, 'noise': 1 } - ] - }; + ], + '/api/v1/entries/?find[date][$lte]=': { + n: 1 + }, +}; describe('admintools', function ( ) { @@ -61,7 +70,7 @@ describe('admintools', function ( ) { before(function (done) { benv.setup(function() { - benv.require(__dirname + '/../tmp/js/bundle.js'); + benv.require(__dirname + '/../tmp/js/bundle.report.js'); self.$ = $; @@ -98,9 +107,14 @@ describe('admintools', function ( ) { if (opts && opts.success && opts.success.call) { if (url.indexOf('/api/v1/treatments.json?&find[created_at][$gte]=')===0) { url = '/api/v1/treatments.json?&find[created_at][$gte]='; - } - if (url.indexOf('/api/v1/entries.json?&find[date][$gte]=')===0) { + } else if (url.indexOf('/api/v1/entries.json?&find[date][$gte]=')===0) { url = '/api/v1/entries.json?&find[date][$gte]='; + } else if (url.indexOf('/api/v1/devicestatus/?find[created_at][$lte]=')===0) { + url = '/api/v1/devicestatus/?find[created_at][$lte]='; + } else if (url.indexOf('/api/v1/treatments/?find[created_at][$lte]=')===0) { + url = '/api/v1/treatments/?find[created_at][$lte]='; + } else if (url.indexOf('/api/v1/entries/?find[date][$lte]=')===0) { + url = '/api/v1/entries/?find[date][$lte]='; } return { done: function mockDone (fn) { @@ -124,7 +138,7 @@ describe('admintools', function ( ) { if (url.indexOf('status.json') > -1) { fn(serverSettings); } else { - fn({message: 'OK'}); + fn({message: {message: 'OK'}}); } return self.$.ajax(); }, @@ -139,7 +153,9 @@ describe('admintools', function ( ) { var d3 = require('d3'); //disable all d3 transitions so most of the other code can run with jsdom - d3.timer = function mockTimer() { }; + //d3.timer = function mockTimer() { }; + let timer = d3.timer(function mockTimer() { }); + timer.stop(); var cookieStorageType = self.localStorage._type @@ -219,6 +235,12 @@ describe('admintools', function ( ) { $('#admin_cleanstatusdb_0_html + button').click(); $('#admin_cleanstatusdb_0_status').text().should.equal('All records removed ...'); // devicestatus code result + $('#admin_cleanstatusdb_1_html + button').text().should.equal('Delete old documents'); // devicestatus button + $('#admin_cleanstatusdb_1_status').text().should.equal(''); // devicestatus init result + + $('#admin_cleanstatusdb_1_html + button').click(); + $('#admin_cleanstatusdb_1_status').text().should.equal('1 records deleted'); // devicestatus code result + $('#admin_futureitems_0_html + button').text().should.equal('Remove treatments in the future'); // futureitems button 0 $('#admin_futureitems_0_status').text().should.equal('Database contains 1 future records'); // futureitems init result 0 @@ -231,6 +253,18 @@ describe('admintools', function ( ) { $('#admin_futureitems_1_html + button').click(); $('#admin_futureitems_1_status').text().should.equal('Record 560983f326c5a592d9b9ae0c removed ...'); // futureitems code result 1 + $('#admin_cleantreatmentsdb_0_html + button').text().should.equal('Delete old documents'); // treatments button + $('#admin_cleantreatmentsdb_0_status').text().should.equal(''); // treatments init result + + $('#admin_cleantreatmentsdb_0_html + button').click(); + $('#admin_cleantreatmentsdb_0_status').text().should.equal('1 records deleted'); // treatments code result + + $('#admin_cleanentriesdb_0_html + button').text().should.equal('Delete old documents'); // entries button + $('#admin_cleanentriesdb_0_status').text().should.equal(''); // entries init result + + $('#admin_cleanentriesdb_0_html + button').click(); + $('#admin_cleanentriesdb_0_status').text().should.equal('1 records deleted'); // entries code result + done(); }); diff --git a/tests/api.devicestatus.test.js b/tests/api.devicestatus.test.js new file mode 100644 index 00000000000..34a0908610e --- /dev/null +++ b/tests/api.devicestatus.test.js @@ -0,0 +1,99 @@ +'use strict'; + +var _ = require('lodash'); +var request = require('supertest'); +var should = require('should'); +var language = require('../lib/language')(); + +describe('Devicestatus API', function ( ) { + this.timeout(10000); + var self = this; + + var api = require('../lib/api/'); + beforeEach(function (done) { + process.env.API_SECRET = 'this is my long pass phrase'; + self.env = require('../env')(); + self.env.settings.authDefaultRoles = 'readable'; + self.env.settings.enable = ['careportal', 'api']; + this.wares = require('../lib/middleware/')(self.env); + self.app = require('express')(); + self.app.enable('api'); + require('../lib/server/bootevent')(self.env, language).boot(function booted(ctx) { + self.ctx = ctx; + self.ctx.ddata = require('../lib/data/ddata')(); + self.app.use('/api', api(self.env, ctx)); + done(); + }); + }); + + after(function () { + // delete process.env.API_SECRET; + }); + + it('post a devicestatus, query, delete, verify gone', function (done) { + // insert a devicestatus - needs to be unique from example data + console.log('Inserting devicestatus entry'); + request(self.app) + .post('/api/devicestatus/') + .set('api-secret', self.env.api_secret || '') + .send({ + device: 'xdripjs://rigName' + , xdripjs: { + state: 6 + , stateString: 'OK' + , txStatus: 0 + , txStatusString: 'OK' + } + , created_at: '2018-12-16T01:00:52Z' + }) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + // make sure devicestatus was inserted successfully + console.log('Ensuring devicestatus entry was inserted successfully'); + request(self.app) + .get('/api/devicestatus/') + .query('find[created_at][$gte]=2018-12-16') + .query('find[created_at][$lte]=2018-12-17') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .expect(function (response) { + response.body[0].xdripjs.state.should.equal(6); + response.body[0].utcOffset.should.equal(0); + }) + .end(function (err) { + if (err) { + done(err); + } else { + // delete the treatment + console.log('Deleting test treatment entry'); + request(self.app) + .delete('/api/devicestatus/') + .query('find[created_at][$gte]=2018-12-16') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + // make sure it was deleted + console.log('Testing if devicestatus was deleted'); + request(self.app) + .get('/api/devicestatus/') + .query('find[created_at][$lte]=2018-12-16') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .expect(function (response) { + response.body.length.should.equal(0); + }) + .end(done); + } + }); + } + }); + } + }); + }); +}); diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index eeb22a86e48..098b5c45663 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -8,19 +8,19 @@ require('should'); describe('Entries REST api', function ( ) { var entries = require('../lib/api/entries/'); + var self = this; this.timeout(10000); before(function (done) { - var env = require('../env')( ); - env.settings.authDefaultRoles = 'readable'; - this.wares = require('../lib/middleware/')(env); - this.archive = null; - this.app = require('express')( ); - this.app.enable('api'); - var self = this; - bootevent(env, language).boot(function booted (ctx) { - self.app.use('/', entries(self.app, self.wares, ctx)); - self.archive = require('../lib/server/entries')(env, ctx); + self.env = require('../env')( ); + self.env.settings.authDefaultRoles = 'readable'; + self.wares = require('../lib/middleware/')(self.env); + self.archive = null; + self.app = require('express')( ); + self.app.enable('api'); + bootevent(self.env, language).boot(function booted (ctx) { + self.app.use('/', entries(self.app, self.wares, ctx, self.env)); + self.archive = require('../lib/server/entries')(self.env, ctx); var creating = load('json'); creating.push({type: 'sgv', sgv: 100, date: Date.now()}); @@ -31,15 +31,15 @@ describe('Entries REST api', function ( ) { beforeEach(function (done) { var creating = load('json'); creating.push({type: 'sgv', sgv: 100, date: Date.now()}); - this.archive.create(creating, done); + self.archive.create(creating, done); }); afterEach(function (done) { - this.archive( ).remove({ }, done); + self.archive( ).remove({ }, done); }); after(function (done) { - this.archive( ).remove({ }, done); + self.archive( ).remove({ }, done); }); // keep this test pinned at or near the top in order to validate all @@ -48,7 +48,7 @@ describe('Entries REST api', function ( ) { // function callback logic in entries.js. it('gets requested number of entries', function (done) { var count = 30; - request(this.app) + request(self.app) .get('/entries.json?find[dateString][$gte]=2014-07-19&count=' + count) .expect(200) .end(function (err, res) { @@ -59,7 +59,7 @@ describe('Entries REST api', function ( ) { it('gets default number of entries', function (done) { var defaultCount = 10; - request(this.app) + request(self.app) .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') .expect(200) .end(function (err, res) { @@ -70,7 +70,7 @@ describe('Entries REST api', function ( ) { it('gets entries in right order', function (done) { var defaultCount = 10; - request(this.app) + request(self.app) .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') .expect(200) .end(function (err, res) { @@ -88,7 +88,7 @@ describe('Entries REST api', function ( ) { it('/echo/ api shows query', function (done) { - request(this.app) + request(self.app) .get('/echo/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') .expect(200) .end(function (err, res) { @@ -102,7 +102,7 @@ describe('Entries REST api', function ( ) { }); it('/slice/ can slice time', function (done) { - var app = this.app; + var app = self.app; request(app) .get('/slice/entries/dateString/sgv/2014-07.json?count=20') .expect(200) @@ -114,7 +114,7 @@ describe('Entries REST api', function ( ) { it('/times/echo can describe query', function (done) { - var app = this.app; + var app = self.app; request(app) .get('/times/echo/2014-07/.*T{00..05}:.json?count=20&find[sgv][$gte]=160') .expect(200) @@ -127,7 +127,7 @@ describe('Entries REST api', function ( ) { }); it('/slice/ can slice with multiple prefix', function (done) { - var app = this.app; + var app = self.app; request(app) .get('/slice/entries/dateString/sgv/2014-07-{17..20}.json?count=20') .expect(200) @@ -138,7 +138,7 @@ describe('Entries REST api', function ( ) { }); it('/slice/ can slice time with prefix and no results', function (done) { - var app = this.app; + var app = self.app; request(app) .get('/slice/entries/dateString/sgv/1999-07.json?count=20&find[sgv][$lte]=401') .expect(200) @@ -149,7 +149,7 @@ describe('Entries REST api', function ( ) { }); it('/times/ can get modal times', function (done) { - var app = this.app; + var app = self.app; request(app) .get('/times/2014-07-/{0..30}T.json?') .expect(200) @@ -160,7 +160,7 @@ describe('Entries REST api', function ( ) { }); it('/times/ can get modal minutes and times', function (done) { - var app = this.app; + var app = self.app; request(app) .get('/times/20{14..15}-07/T{09..10}.json?') .expect(200) @@ -170,7 +170,7 @@ describe('Entries REST api', function ( ) { }); }); it('/times/ can get multiple prefixen and modal minutes and times', function (done) { - var app = this.app; + var app = self.app; request(app) .get('/times/20{14..15}/T.*:{00..60}.json?') .expect(200) @@ -181,7 +181,7 @@ describe('Entries REST api', function ( ) { }); it('/entries/current.json', function (done) { - request(this.app) + request(self.app) .get('/entries/current.json') .expect(200) .end(function (err, res) { @@ -192,8 +192,8 @@ describe('Entries REST api', function ( ) { }); it('/entries/:id', function (done) { - var app = this.app; - this.archive.list({count: 1}, function(err, records) { + var app = self.app; + self.archive.list({count: 1}, function(err, records) { var currentId = records.pop()._id.toString(); request(app) .get('/entries/'+currentId+'.json') @@ -207,7 +207,7 @@ describe('Entries REST api', function ( ) { }); it('/entries/:model', function (done) { - var app = this.app; + var app = self.app; request(app) .get('/entries/sgv/.json?count=10&find[dateString][$gte]=2014') .expect(200) @@ -218,7 +218,7 @@ describe('Entries REST api', function ( ) { }); it('disallow POST by readable /entries/preview', function (done) { - request(this.app) + request(self.app) .post('/entries/preview.json') .send(load('json')) .expect(401) @@ -229,7 +229,7 @@ describe('Entries REST api', function ( ) { }); it('disallow deletes unauthorized', function (done) { - var app = this.app; + var app = self.app; request(app) .delete('/entries/sgv?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') @@ -249,4 +249,62 @@ describe('Entries REST api', function ( ) { }); }); + it('post an entry, query, delete, verify gone', function (done) { + // insert a glucose entry - needs to be unique from example data + console.log('Inserting glucose entry') + request(self.app) + .post('/entries/') + .set('api-secret', self.env.api_secret || '') + .send({ + "type": "sgv", "sgv": "199", "dateString": "2014-07-20T00:44:15.000-07:00" + , "date": 1405791855000, "device": "dexcom", "direction": "NOT COMPUTABLE" + }) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + // make sure treatment was inserted successfully + console.log('Ensuring glucose entry was inserted successfully'); + request(self.app) + .get('/entries.json?find[dateString][$gte]=2014-07-20&count=100') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .expect(function (response) { + var entry = response.body[0]; + entry.sgv.should.equal('199'); + entry.utcOffset.should.equal(-420); + }) + .end(function (err) { + if (err) { + done(err); + } else { + // delete the glucose entry + console.log('Deleting test glucose entry'); + request(self.app) + .delete('/entries.json?find[dateString][$gte]=2014-07-20&count=100') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + // make sure it was deleted + console.log('Testing if glucose entry was deleted'); + request(self.app) + .get('/entries.json?find[dateString][$gte]=2014-07-20&count=100') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .expect(function (response) { + response.body.length.should.equal(0); + }) + .end(done); + } + }); + } + }); + } + }); + }); + }); diff --git a/tests/api.root.test.js b/tests/api.root.test.js new file mode 100644 index 00000000000..d2c3609a117 --- /dev/null +++ b/tests/api.root.test.js @@ -0,0 +1,42 @@ +'use strict'; + +const request = require('supertest'); +require('should'); + +describe('Root REST API', function() { + const self = this + , instance = require('./fixtures/api/instance') + , semver = require('semver') + ; + + this.timeout(15000); + + before(async () => { + self.instance = await instance.create({}); + self.app = self.instance.app; + self.env = self.instance.env; + }); + + + after(function after () { + self.instance.server.close(); + }); + + + it('GET /api/versions', async () => { + let res = await request(self.app) + .get('/api/versions') + .expect(200); + + res.body.length.should.be.aboveOrEqual(3); + res.body.forEach(obj => { + const fields = Object.getOwnPropertyNames(obj); + fields.sort().should.be.eql(['url', 'version']); + + semver.valid(obj.version).should.be.ok(); + obj.url.should.startWith('/api'); + }); + }); + +}); + diff --git a/tests/api.treatments.test.js b/tests/api.treatments.test.js index 6d0d5deb933..4ba3739f4c0 100644 --- a/tests/api.treatments.test.js +++ b/tests/api.treatments.test.js @@ -4,6 +4,7 @@ var _ = require('lodash'); var request = require('supertest'); var should = require('should'); var language = require('../lib/language')(); +var _moment = require('moment'); describe('Treatment API', function ( ) { this.timeout(10000); @@ -60,6 +61,44 @@ describe('Treatment API', function ( ) { }); }); + + it('post single treatments in zoned time format', function (done) { + + var current_time = Date.now(); + console.log('Testing date with local format: ', _moment(current_time).format("YYYY-MM-DDTHH:mm:ss.SSSZZ")); + + self.ctx.treatments().remove({ }, function ( ) { + request(self.app) + .post('/api/treatments/') + .set('api-secret', self.env.api_secret || '') + .send({eventType: 'Meal Bolus', created_at: _moment(current_time).format("YYYY-MM-DDTHH:mm:ss.SSSZZ"), carbs: '30', insulin: '2.00', glucose: 100, glucoseType: 'Finger', units: 'mg/dl'}) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + self.ctx.treatments.list({}, function (err, list) { + var sorted = _.sortBy(list, function (treatment) { + return treatment.created_at; + }); + console.log(sorted); + sorted.length.should.equal(1); + sorted[0].glucose.should.equal(100); + should.not.exist(sorted[0].eventTime); + sorted[0].insulin.should.equal(2); + sorted[0].carbs.should.equal(30); + var zonedTime = _moment(current_time).utc().format("YYYY-MM-DDTHH:mm:ss.SSS") + "Z"; + sorted[0].created_at.should.equal(zonedTime); + sorted[0].utcOffset.should.equal(-1* new Date().getTimezoneOffset()); + done(); + }); + } + }); + + }); + }); + + it('post a treatment array', function (done) { self.ctx.treatments().remove({ }, function ( ) { request(self.app) @@ -101,7 +140,7 @@ describe('Treatment API', function ( ) { , {eventType: 'BG Check', glucose: 100, units: 'mg/dl', created_at: now} , {eventType: 'BG Check', glucose: 100, units: 'mg/dl', created_at: now} , {eventType: 'BG Check', glucose: 100, units: 'mg/dl', created_at: now} - , {eventType: 'Meal Bolus', carbs: '30', insulin: '2.00', preBolus: '15', glucose: 100, glucoseType: 'Finger', units: 'mg/dl'} + , {eventType: 'Meal Bolus', created_at: now, carbs: '30', insulin: '2.00', preBolus: '15', glucose: 100, glucoseType: 'Finger', units: 'mg/dl'} ]) .expect(200) .end(function (err) { @@ -125,4 +164,60 @@ describe('Treatment API', function ( ) { }); }); }); + + it('post a treatment, query, delete, verify gone', function (done) { + // insert a treatment - needs to be unique from example data + console.log('Inserting treatment entry'); + request(self.app) + .post('/api/treatments/') + .set('api-secret', self.env.api_secret || '') + .send({eventType: 'Meal Bolus', carbs: '99', insulin: '2.00', preBolus: '15', glucose: 100, glucoseType: 'Finger', units: 'mg/dl'}) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + // make sure treatment was inserted successfully + console.log('Ensuring treatment entry was inserted successfully'); + request(self.app) + .get('/api/treatments/') + .query('find[carbs]=99') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .expect(function (response) { + response.body[0].carbs.should.equal(99); + }) + .end(function (err) { + if (err) { + done(err); + } else { + // delete the treatment + console.log('Deleting test treatment entry'); + request(self.app) + .delete('/api/treatments/') + .query('find[carbs]=99') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .end(function (err) { + if (err) { + done(err); + } else { + // make sure it was deleted + console.log('Testing if entry was deleted'); + request(self.app) + .get('/api/treatments/') + .query('find[carbs]=99') + .set('api-secret', self.env.api_secret || '') + .expect(200) + .expect(function (response) { + response.body.length.should.equal(0); + }) + .end(done); + } + }); + } + }); + } + }); + }); }); diff --git a/tests/api.unauthorized.test.js b/tests/api.unauthorized.test.js index 8c804c4244f..5785069f77e 100644 --- a/tests/api.unauthorized.test.js +++ b/tests/api.unauthorized.test.js @@ -21,7 +21,7 @@ describe('authed REST api', function ( ) { var self = this; self.known_key = known; require('../lib/server/bootevent')(env, language).boot(function booted (ctx) { - self.app.use('/', entries(self.app, self.wares, ctx)); + self.app.use('/', entries(self.app, self.wares, ctx, env)); self.archive = require('../lib/server/entries')(env, ctx); var creating = load('json'); diff --git a/tests/api.verifyauth.test.js b/tests/api.verifyauth.test.js index a9fd681da7b..48a05f2d9c4 100644 --- a/tests/api.verifyauth.test.js +++ b/tests/api.verifyauth.test.js @@ -26,7 +26,7 @@ describe('Verifyauth REST api', function ( ) { .get('/api/verifyauth') .expect(200) .end(function(err, res) { - res.body.message.should.equal('UNAUTHORIZED'); + res.body.message.message.should.equal('UNAUTHORIZED'); done(); }); }); @@ -37,7 +37,7 @@ describe('Verifyauth REST api', function ( ) { .set('api-secret', self.env.api_secret || '') .expect(200) .end(function(err, res) { - res.body.message.should.equal('OK'); + res.body.message.message.should.equal('OK'); done(); }); }); diff --git a/tests/api3.basic.test.js b/tests/api3.basic.test.js new file mode 100644 index 00000000000..8e51b343585 --- /dev/null +++ b/tests/api3.basic.test.js @@ -0,0 +1,49 @@ +'use strict'; + +const request = require('supertest'); +require('should'); + +describe('Basic REST API3', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , instance = require('./fixtures/api3/instance') + ; + + this.timeout(15000); + + before(async () => { + self.instance = await instance.create({}); + self.app = self.instance.app; + self.env = self.instance.env; + }); + + + after(function after () { + self.instance.server.close(); + }); + + + it('GET /swagger', async () => { + let res = await request(self.app) + .get('/api/v3/swagger.yaml') + .expect(200); + + res.header['content-length'].should.be.above(0); + }); + + + it('GET /version', async () => { + let res = await request(self.app) + .get('/api/v3/version') + .expect(200); + + const apiConst = require('../lib/api3/const.json') + , software = require('../package.json'); + + res.body.version.should.equal(software.version); + res.body.apiVersion.should.equal(apiConst.API3_VERSION); + res.body.srvDate.should.be.within(testConst.YEAR_2019, testConst.YEAR_2050); + }); + +}); + diff --git a/tests/api3.create.test.js b/tests/api3.create.test.js new file mode 100644 index 00000000000..cbd17a3e826 --- /dev/null +++ b/tests/api3.create.test.js @@ -0,0 +1,487 @@ +/* eslint require-atomic-updates: 0 */ +/* global should */ +'use strict'; + +require('should'); + +describe('API3 CREATE', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + , opTools = require('../lib/api3/shared/operationTools') + , utils = require('./fixtures/api3/utils') + ; + + self.validDoc = { + date: (new Date()).getTime(), + app: testConst.TEST_APP, + device: testConst.TEST_DEVICE, + eventType: 'Correction Bolus', + insulin: 0.3 + }; + self.validDoc.identifier = opTools.calculateIdentifier(self.validDoc); + + self.timeout(20000); + + + /** + * Cleanup after successful creation + */ + self.delete = async function deletePermanent (identifier) { + await self.instance.delete(`${self.url}/${identifier}?permanent=true&token=${self.token.delete}`) + .expect(204); + }; + + + /** + * Get document detail for futher processing + */ + self.get = async function get (identifier) { + let res = await self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + .expect(200); + + return res.body; + }; + + + /** + * Get document detail for futher processing + */ + self.search = async function search (date) { + let res = await self.instance.get(`${self.url}?date$eq=${date}&token=${self.token.read}`) + .expect(200); + + return res.body; + }; + + + before(async () => { + self.instance = await instance.create({}); + + self.app = self.instance.app; + self.env = self.instance.env; + self.url = '/api/v3/treatments'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + self.urlToken = `${self.url}?token=${self.token.create}`; + }); + + + after(() => { + self.instance.server.close(); + }); + + + it('should require authentication', async () => { + let res = await self.instance.post(`${self.url}`) + .send(self.validDoc) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Missing or bad access token or JWT'); + }); + + + it('should not found not existing collection', async () => { + let res = await self.instance.post(`/api/v3/NOT_EXIST?token=${self.url}`) + .send(self.validDoc) + .expect(404); + + res.body.should.be.empty(); + }); + + + it('should require create permission', async () => { + let res = await self.instance.post(`${self.url}?token=${self.token.read}`) + .send(self.validDoc) + .expect(403); + + res.body.status.should.equal(403); + res.body.message.should.equal('Missing permission api:treatments:create'); + }); + + + it('should reject empty body', async () => { + await self.instance.post(self.urlToken) + .send({ }) + .expect(400); + }); + + + it('should accept valid document', async () => { + let res = await self.instance.post(self.urlToken) + .send(self.validDoc) + .expect(201); + + res.body.should.be.empty(); + res.headers.location.should.equal(`${self.url}/${self.validDoc.identifier}`); + const lastModified = new Date(res.headers['last-modified']).getTime(); // Last-Modified has trimmed milliseconds + + let body = await self.get(self.validDoc.identifier); + body.should.containEql(self.validDoc); + + const ms = body.srvModified % 1000; + (body.srvModified - ms).should.equal(lastModified); + (body.srvCreated - ms).should.equal(lastModified); + body.subject.should.equal(self.subject.apiCreate.name); + + await self.delete(self.validDoc.identifier); + }); + + + it('should reject missing date', async () => { + let doc = Object.assign({}, self.validDoc); + delete doc.date; + + let res = await self.instance.post(self.urlToken) + .send(doc) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing date field'); + }); + + + it('should reject invalid date null', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { date: null })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing date field'); + }); + + + it('should reject invalid date ABC', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { date: 'ABC' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing date field'); + }); + + + it('should reject invalid date -1', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { date: -1 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing date field'); + }); + + + + it('should reject invalid date 1 (too old)', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { date: 1 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing date field'); + }); + + + it('should reject invalid date - illegal format', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { date: '2019-20-60T50:90:90' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing date field'); + }); + + + it('should reject invalid utcOffset -5000', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { utcOffset: -5000 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing utcOffset field'); + }); + + + it('should reject invalid utcOffset ABC', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { utcOffset: 'ABC' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing utcOffset field'); + }); + + + it('should accept valid utcOffset', async () => { + await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { utcOffset: 120 })) + .expect(201); + + let body = await self.get(self.validDoc.identifier); + body.utcOffset.should.equal(120); + await self.delete(self.validDoc.identifier); + }); + + + it('should reject invalid utcOffset null', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { utcOffset: null })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing utcOffset field'); + }); + + + it('should reject missing app', async () => { + let doc = Object.assign({}, self.validDoc); + delete doc.app; + + let res = await self.instance.post(self.urlToken) + .send(doc) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing app field'); + }); + + + it('should reject invalid app null', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { app: null })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing app field'); + }); + + + it('should reject empty app', async () => { + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { app: '' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Bad or missing app field'); + }); + + + it('should normalize date and store utcOffset', async () => { + await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { date: '2019-06-10T08:07:08,576+02:00' })) + .expect(201); + + let body = await self.get(self.validDoc.identifier); + body.date.should.equal(1560146828576); + body.utcOffset.should.equal(120); + await self.delete(self.validDoc.identifier); + }); + + + it('should require update permission for deduplication', async () => { + self.validDoc.date = (new Date()).getTime(); + self.validDoc.identifier = utils.randomString('32', 'aA#'); + + const doc = Object.assign({}, self.validDoc); + + await self.instance.post(self.urlToken) + .send(doc) + .expect(201); + + let createdBody = await self.get(doc.identifier); + createdBody.should.containEql(doc); + + const doc2 = Object.assign({}, doc); + let res = await self.instance.post(self.urlToken) + .send(doc2) + .expect(403); + + res.body.status.should.equal(403); + res.body.message.should.equal('Missing permission api:treatments:update'); + await self.delete(doc.identifier); + }); + + + it('should deduplicate document by identifier', async () => { + self.validDoc.date = (new Date()).getTime(); + self.validDoc.identifier = utils.randomString('32', 'aA#'); + + const doc = Object.assign({}, self.validDoc); + + await self.instance.post(self.urlToken) + .send(doc) + .expect(201); + + let createdBody = await self.get(doc.identifier); + createdBody.should.containEql(doc); + + const doc2 = Object.assign({}, doc, { + insulin: 0.5 + }); + + await self.instance.post(`${self.url}?token=${self.token.all}`) + .send(doc2) + .expect(204); + + let updatedBody = await self.get(doc2.identifier); + updatedBody.should.containEql(doc2); + + await self.delete(doc2.identifier); + }); + + + it('should deduplicate document by created_at+eventType', async () => { + self.validDoc.date = (new Date()).getTime(); + self.validDoc.identifier = utils.randomString('32', 'aA#'); + + const doc = Object.assign({}, self.validDoc, { + created_at: new Date(self.validDoc.date).toISOString() + }); + delete doc.identifier; + + self.instance.ctx.treatments.create([doc], async (err) => { // let's insert the document in APIv1's way + should.not.exist(err); + + const doc2 = Object.assign({}, doc, { + insulin: 0.4, + identifier: utils.randomString('32', 'aA#') + }); + delete doc2._id; // APIv1 updates input document, we must get rid of _id for the next round + + await self.instance.post(`${self.url}?token=${self.token.all}`) + .send(doc2) + .expect(204); + + let updatedBody = await self.get(doc2.identifier); + updatedBody.should.containEql(doc2); + + await self.delete(doc2.identifier); + }); + }); + + + it('should not deduplicate treatment only by created_at', async () => { + self.validDoc.date = (new Date()).getTime(); + self.validDoc.identifier = utils.randomString('32', 'aA#'); + + const doc = Object.assign({}, self.validDoc, { + created_at: new Date(self.validDoc.date).toISOString() + }); + delete doc.identifier; + + self.instance.ctx.treatments.create([doc], async (err) => { // let's insert the document in APIv1's way + should.not.exist(err); + + let oldBody = await self.get(doc._id); + delete doc._id; // APIv1 updates input document, we must get rid of _id for the next round + oldBody.should.containEql(doc); + + const doc2 = Object.assign({}, doc, { + eventType: 'Meal Bolus', + insulin: 0.4, + identifier: utils.randomString('32', 'aA#') + }); + + await self.instance.post(`${self.url}?token=${self.token.all}`) + .send(doc2) + .expect(201); + + let updatedBody = await self.get(doc2.identifier); + updatedBody.should.containEql(doc2); + updatedBody.identifier.should.not.equal(oldBody.identifier); + + await self.delete(doc2.identifier); + await self.delete(oldBody.identifier); + }); + }); + + + it('should overwrite deleted document', async () => { + const date1 = new Date() + , identifier = utils.randomString('32', 'aA#'); + + await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { identifier, date: date1.toISOString() })) + .expect(201); + + await self.instance.delete(`${self.url}/${identifier}?token=${self.token.delete}`) + .expect(204); + + const date2 = new Date(); + let res = await self.instance.post(self.urlToken) + .send(Object.assign({}, self.validDoc, { identifier, date: date2.toISOString() })) + .expect(403); + + res.body.status.should.be.equal(403); + res.body.message.should.be.equal('Missing permission api:treatments:update'); + + res = await self.instance.post(`${self.url}?token=${self.token.all}`) + .send(Object.assign({}, self.validDoc, { identifier, date: date2.toISOString() })) + .expect(204); + + res.body.should.be.empty(); + + let body = await self.get(identifier); + body.date.should.equal(date2.getTime()); + body.identifier.should.equal(identifier); + await self.delete(identifier); + }); + + + it('should calculate the identifier', async () => { + self.validDoc.date = (new Date()).getTime(); + delete self.validDoc.identifier; + const validIdentifier = opTools.calculateIdentifier(self.validDoc); + + let res = await self.instance.post(self.urlToken) + .send(self.validDoc) + .expect(201); + + res.body.should.be.empty(); + res.headers.location.should.equal(`${self.url}/${validIdentifier}`); + self.validDoc.identifier = validIdentifier; + + let body = await self.get(validIdentifier); + body.should.containEql(self.validDoc); + await self.delete(validIdentifier); + }); + + + it('should deduplicate by identifier calculation', async () => { + self.validDoc.date = (new Date()).getTime(); + delete self.validDoc.identifier; + const validIdentifier = opTools.calculateIdentifier(self.validDoc); + + let res = await self.instance.post(self.urlToken) + .send(self.validDoc) + .expect(201); + + res.body.should.be.empty(); + res.headers.location.should.equal(`${self.url}/${validIdentifier}`); + self.validDoc.identifier = validIdentifier; + + let body = await self.get(validIdentifier); + body.should.containEql(self.validDoc); + + delete self.validDoc.identifier; + res = await self.instance.post(`${self.url}?token=${self.token.update}`) + .send(self.validDoc) + .expect(204); + + res.body.should.be.empty(); + res.headers.location.should.equal(`${self.url}/${validIdentifier}`); + self.validDoc.identifier = validIdentifier; + + body = await self.search(self.validDoc.date); + body.length.should.equal(1); + + await self.delete(validIdentifier); + }); + +}); + diff --git a/tests/api3.delete.test.js b/tests/api3.delete.test.js new file mode 100644 index 00000000000..203d32edce8 --- /dev/null +++ b/tests/api3.delete.test.js @@ -0,0 +1,53 @@ +/* eslint require-atomic-updates: 0 */ +'use strict'; + +require('should'); + +describe('API3 UPDATE', function() { + const self = this + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + ; + + self.timeout(15000); + + + before(async () => { + self.instance = await instance.create({}); + + self.app = self.instance.app; + self.env = self.instance.env; + self.url = '/api/v3/treatments'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + self.urlToken = `${self.url}?token=${self.token.delete}`; + }); + + + after(() => { + self.instance.server.close(); + }); + + + it('should require authentication', async () => { + let res = await self.instance.delete(`${self.url}/FAKE_IDENTIFIER`) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Missing or bad access token or JWT'); + }); + + + it('should not found not existing collection', async () => { + let res = await self.instance.delete(`/api/v3/NOT_EXIST?token=${self.url}`) + .send(self.validDoc) + .expect(404); + + res.body.should.be.empty(); + }); + +}); + diff --git a/tests/api3.generic.workflow.test.js b/tests/api3.generic.workflow.test.js new file mode 100644 index 00000000000..acebe39555a --- /dev/null +++ b/tests/api3.generic.workflow.test.js @@ -0,0 +1,295 @@ +/* eslint require-atomic-updates: 0 */ +'use strict'; + +require('should'); + +describe('Generic REST API3', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + , opTools = require('../lib/api3/shared/operationTools') + , utils = require('./fixtures/api3/utils') + ; + + utils.randomString('32', 'aA#'); // let's have a brand new identifier for your testing document + self.urlLastModified = '/api/v3/lastModified'; + self.historyTimestamp = 0; + + self.docOriginal = { + eventType: 'Correction Bolus', + insulin: 1, + date: (new Date()).getTime(), + app: testConst.TEST_APP, + device: testConst.TEST_DEVICE + }; + self.identifier = opTools.calculateIdentifier(self.docOriginal); + self.docOriginal.identifier = self.identifier; + + this.timeout(30000); + + before(async () => { + self.instance = await instance.create({}); + + self.app = self.instance.app; + self.env = self.instance.env; + self.urlCol = '/api/v3/treatments'; + self.urlResource = self.urlCol + '/' + self.identifier; + self.urlHistory = self.urlCol + '/history'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + self.urlToken = `${self.url}?token=${self.token.create}`; + }); + + + after(() => { + self.instance.server.close(); + }); + + + self.checkHistoryExistence = async function checkHistoryExistence (assertions) { + + let res = await self.instance.get(`${self.urlHistory}/${self.historyTimestamp}?token=${self.token.read}`) + .expect(200); + + res.body.length.should.be.above(0); + res.body.should.matchAny(value => { + value.identifier.should.be.eql(self.identifier); + value.srvModified.should.be.above(self.historyTimestamp); + + if (typeof(assertions) === 'function') { + assertions(value); + } + + self.historyTimestamp = value.srvModified; + }); + }; + + + it('LAST MODIFIED to get actual server timestamp', async () => { + let res = await self.instance.get(`${self.urlLastModified}?token=${self.token.read}`) + .expect(200); + + self.historyTimestamp = res.body.collections.treatments; + if (!self.historyTimestamp) { + self.historyTimestamp = res.body.srvDate - (10 * 60 * 1000); + } + self.historyTimestamp.should.be.aboveOrEqual(testConst.YEAR_2019); + }); + + + it('STATUS to get actual server timestamp', async () => { + let res = await self.instance.get(`/api/v3/status?token=${self.token.read}`) + .expect(200); + + self.historyTimestamp = res.body.srvDate; + self.historyTimestamp.should.be.aboveOrEqual(testConst.YEAR_2019); + }); + + + it('READ of not existing document is not found', async () => { + await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + .expect(404); + }); + + + it('SEARCH of not existing document (not found)', async () => { + let res = await self.instance.get(`${self.urlCol}?token=${self.token.read}`) + .query({ 'identifier_eq': self.identifier }) + .expect(200); + + res.body.should.have.length(0); + }); + + + it('DELETE of not existing document is not found', async () => { + await self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + .expect(404); + }); + + + it('CREATE new document', async () => { + await self.instance.post(`${self.urlCol}?token=${self.token.create}`) + .send(self.docOriginal) + .expect(201); + }); + + + it('READ existing document', async () => { + let res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + .expect(200); + + res.body.should.containEql(self.docOriginal); + self.docActual = res.body; + + if (self.historyTimestamp >= self.docActual.srvModified) { + self.historyTimestamp = self.docActual.srvModified - 1; + } + }); + + + it('SEARCH existing document (found)', async () => { + let res = await self.instance.get(`${self.urlCol}?token=${self.token.read}`) + .query({ 'identifier$eq': self.identifier }) + .expect(200); + + res.body.length.should.be.above(0); + res.body.should.matchAny(value => { + value.identifier.should.be.eql(self.identifier); + }); + }); + + + it('new document in HISTORY', async () => { + await self.checkHistoryExistence(); + }); + + + it('UPDATE document', async () => { + self.docActual.insulin = 0.5; + + await self.instance.put(`${self.urlResource}?token=${self.token.update}`) + .send(self.docActual) + .expect(204); + + self.docActual.subject = self.subject.apiUpdate.name; + }); + + + it('document changed in HISTORY', async () => { + await self.checkHistoryExistence(); + }); + + + it('document changed in READ', async () => { + let res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + .expect(200); + + delete self.docActual.srvModified; + res.body.should.containEql(self.docActual); + self.docActual = res.body; + }); + + + it('PATCH document', async () => { + self.docActual.carbs = 5; + self.docActual.insulin = 0.4; + + await self.instance.patch(`${self.urlResource}?token=${self.token.update}`) + .send({ 'carbs': self.docActual.carbs, 'insulin': self.docActual.insulin }) + .expect(204); + }); + + + it('document changed in HISTORY', async () => { + await self.checkHistoryExistence(); + }); + + + it('document changed in READ', async () => { + let res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + .expect(200); + + delete self.docActual.srvModified; + res.body.should.containEql(self.docActual); + self.docActual = res.body; + }); + + + it('soft DELETE', async () => { + await self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + .expect(204); + }); + + + it('READ of deleted is gone', async () => { + await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + .expect(410); + }); + + + + it('SEARCH of deleted document missing it', async () => { + let res = await self.instance.get(`${self.urlCol}?token=${self.token.read}`) + .query({ 'identifier_eq': self.identifier }) + .expect(200); + + res.body.should.have.length(0); + }); + + + it('document deleted in HISTORY', async () => { + await self.checkHistoryExistence(value => { + value.isValid.should.be.eql(false); + }); + }); + + + it('permanent DELETE', async () => { + await self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + .query({ 'permanent': 'true' }) + .expect(204); + }); + + + it('READ of permanently deleted is not found', async () => { + await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + .expect(404); + }); + + + it('document permanently deleted not in HISTORY', async () => { + let res = await self.instance.get(`${self.urlHistory}/${self.historyTimestamp}?token=${self.token.read}`); + + if (res.status === 200) { + res.body.should.matchEach(value => { + value.identifier.should.not.be.eql(self.identifier); + }); + } else { + res.status.should.equal(204); + } + }); + + + it('should not modify read-only document', async () => { + await self.instance.post(`${self.urlCol}?token=${self.token.create}`) + .send(Object.assign({}, self.docOriginal, { isReadOnly: true })) + .expect(201); + + let res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + .expect(200); + + self.docActual = res.body; + delete self.docActual.srvModified; + const readOnlyMessage = 'Trying to modify read-only document'; + + res = await self.instance.post(`${self.urlCol}?token=${self.token.update}`) + .send(Object.assign({}, self.docActual, { insulin: 0.41 })) + .expect(422); + res.body.message.should.equal(readOnlyMessage); + + res = await self.instance.put(`${self.urlResource}?token=${self.token.update}`) + .send(Object.assign({}, self.docActual, { insulin: 0.42 })) + .expect(422); + res.body.message.should.equal(readOnlyMessage); + + res = await self.instance.patch(`${self.urlResource}?token=${self.token.update}`) + .send({ insulin: 0.43 }) + .expect(422); + res.body.message.should.equal(readOnlyMessage); + + res = await self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + .query({ 'permanent': 'true' }) + .expect(422); + res.body.message.should.equal(readOnlyMessage); + + res = await self.instance.get(`${self.urlResource}?token=${self.token.read}`) + .expect(200); + res.body.should.containEql(self.docOriginal); + }); + +}); + diff --git a/tests/api3.patch.test.js b/tests/api3.patch.test.js new file mode 100644 index 00000000000..38850b46ad5 --- /dev/null +++ b/tests/api3.patch.test.js @@ -0,0 +1,219 @@ +/* eslint require-atomic-updates: 0 */ +'use strict'; + +require('should'); + +describe('API3 PATCH', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + , opTools = require('../lib/api3/shared/operationTools') + ; + + self.validDoc = { + date: (new Date()).getTime(), + utcOffset: -180, + app: testConst.TEST_APP, + device: testConst.TEST_DEVICE, + eventType: 'Correction Bolus', + insulin: 0.3 + }; + self.validDoc.identifier = opTools.calculateIdentifier(self.validDoc); + + self.timeout(15000); + + + /** + * Get document detail for futher processing + */ + self.get = async function get (identifier) { + let res = await self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + .expect(200); + + return res.body; + }; + + + before(async () => { + self.instance = await instance.create({}); + + self.app = self.instance.app; + self.env = self.instance.env; + self.url = '/api/v3/treatments'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + self.urlToken = `${self.url}/${self.validDoc.identifier}?token=${self.token.update}`; + }); + + + after(() => { + self.instance.server.close(); + }); + + + it('should require authentication', async () => { + let res = await self.instance.patch(`${self.url}/FAKE_IDENTIFIER`) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Missing or bad access token or JWT'); + }); + + + it('should not found not existing collection', async () => { + let res = await self.instance.patch(`/api/v3/NOT_EXIST?token=${self.url}`) + .send(self.validDoc) + .expect(404); + + res.body.should.be.empty(); + }); + + + it('should not found not existing document', async () => { + let res = await self.instance.patch(self.urlToken) + .send(self.validDoc) + .expect(404); + + res.body.should.be.empty(); + + // now let's insert the document for further patching + res = await self.instance.post(`${self.url}?token=${self.token.create}`) + .send(self.validDoc) + .expect(201); + + res.body.should.be.empty(); + }); + + + it('should reject identifier alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { identifier: 'MODIFIED'})) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field identifier cannot be modified by the client'); + }); + + + it('should reject date alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { date: self.validDoc.date + 10000 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field date cannot be modified by the client'); + }); + + + it('should reject utcOffset alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { utcOffset: self.utcOffset - 120 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field utcOffset cannot be modified by the client'); + }); + + + it('should reject eventType alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { eventType: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field eventType cannot be modified by the client'); + }); + + + it('should reject device alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { device: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field device cannot be modified by the client'); + }); + + + it('should reject app alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { app: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field app cannot be modified by the client'); + }); + + + it('should reject srvCreated alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { srvCreated: self.validDoc.date - 10000 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field srvCreated cannot be modified by the client'); + }); + + + it('should reject subject alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { subject: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field subject cannot be modified by the client'); + }); + + + it('should reject srvModified alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { srvModified: self.validDoc.date - 100000 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field srvModified cannot be modified by the client'); + }); + + + it('should reject modifiedBy alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { modifiedBy: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field modifiedBy cannot be modified by the client'); + }); + + + it('should reject isValid alteration', async () => { + let res = await self.instance.patch(self.urlToken) + .send(Object.assign({}, self.validDoc, { isValid: false })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field isValid cannot be modified by the client'); + }); + + + it('should patch document', async () => { + self.validDoc.carbs = 10; + + let res = await self.instance.patch(self.urlToken) + .send(self.validDoc) + .expect(204); + + res.body.should.be.empty(); + + let body = await self.get(self.validDoc.identifier); + body.carbs.should.equal(10); + body.insulin.should.equal(0.3); + body.subject.should.equal(self.subject.apiCreate.name); + body.modifiedBy.should.equal(self.subject.apiUpdate.name); + }); + +}); + diff --git a/tests/api3.read.test.js b/tests/api3.read.test.js new file mode 100644 index 00000000000..b18b0225bb9 --- /dev/null +++ b/tests/api3.read.test.js @@ -0,0 +1,180 @@ +/* eslint require-atomic-updates: 0 */ +/* global should */ +'use strict'; + +require('should'); + +describe('API3 READ', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + , opTools = require('../lib/api3/shared/operationTools') + ; + + self.validDoc = { + date: (new Date()).getTime(), + app: testConst.TEST_APP, + device: testConst.TEST_DEVICE, + uploaderBattery: 58 + }; + self.validDoc.identifier = opTools.calculateIdentifier(self.validDoc); + + self.timeout(15000); + + + before(async () => { + self.instance = await instance.create({}); + + self.app = self.instance.app; + self.env = self.instance.env; + self.url = '/api/v3/devicestatus'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + }); + + + after(() => { + self.instance.server.close(); + }); + + + it('should require authentication', async () => { + let res = await self.instance.get(`${self.url}/FAKE_IDENTIFIER`) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Missing or bad access token or JWT'); + }); + + + it('should not found not existing collection', async () => { + let res = await self.instance.get(`/api/v3/NOT_EXIST/NOT_EXIST?token=${self.url}`) + .send(self.validDoc) + .expect(404); + + res.body.should.be.empty(); + }); + + + it('should not found not existing document', async () => { + await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + .expect(404); + }); + + + it('should read just created document', async () => { + let res = await self.instance.post(`${self.url}?token=${self.token.create}`) + .send(self.validDoc) + .expect(201); + + res.body.should.be.empty(); + + res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + .expect(200); + + res.body.should.containEql(self.validDoc); + res.body.should.have.property('srvCreated').which.is.a.Number(); + res.body.should.have.property('srvModified').which.is.a.Number(); + res.body.should.have.property('subject'); + self.validDoc.subject = res.body.subject; // let's store subject for later tests + }); + + + it('should contain only selected fields', async () => { + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?fields=date,device,subject&token=${self.token.read}`) + .expect(200); + + const correct = { + date: self.validDoc.date, + device: self.validDoc.device, + subject: self.validDoc.subject + }; + res.body.should.eql(correct); + }); + + + it('should contain all fields', async () => { + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?fields=_all&token=${self.token.read}`) + .expect(200); + + for (let fieldName of ['app', 'date', 'device', 'identifier', 'srvModified', 'uploaderBattery', 'subject']) { + res.body.should.have.property(fieldName); + } + }); + + + it('should not send unmodified document since', async () => { + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + .set('If-Modified-Since', new Date(new Date().getTime() + 1000).toUTCString()) + .expect(304); + + res.body.should.be.empty(); + }); + + + it('should send modified document since', async () => { + let res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + .set('If-Modified-Since', new Date(new Date(self.validDoc.date).getTime() - 1000).toUTCString()) + .expect(200); + + res.body.should.containEql(self.validDoc); + }); + + + it('should recognize softly deleted document', async () => { + let res = await self.instance.delete(`${self.url}/${self.validDoc.identifier}?token=${self.token.delete}`) + .expect(204); + + res.body.should.be.empty(); + + res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + .expect(410); + + res.body.should.be.empty(); + }); + + + it('should not found permanently deleted document', async () => { + let res = await self.instance.delete(`${self.url}/${self.validDoc.identifier}?permanent=true&token=${self.token.delete}`) + .expect(204); + + res.body.should.be.empty(); + + res = await self.instance.get(`${self.url}/${self.validDoc.identifier}?token=${self.token.read}`) + .expect(404); + + res.body.should.be.empty(); + }); + + + it('should found document created by APIv1', async () => { + + const doc = Object.assign({}, self.validDoc, { + created_at: new Date(self.validDoc.date).toISOString() + }); + delete doc.identifier; + + self.instance.ctx.devicestatus.create([doc], async (err) => { // let's insert the document in APIv1's way + should.not.exist(err); + const identifier = doc._id.toString(); + delete doc._id; + + let res = await self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + .expect(200); + + res.body.should.containEql(doc); + + res = await self.instance.delete(`${self.url}/${identifier}?permanent=true&token=${self.token.delete}`) + .expect(204); + + res.body.should.be.empty(); + }); + }); + + +}); + diff --git a/tests/api3.search.test.js b/tests/api3.search.test.js new file mode 100644 index 00000000000..dae0ebaaf34 --- /dev/null +++ b/tests/api3.search.test.js @@ -0,0 +1,261 @@ +/* eslint require-atomic-updates: 0 */ +/* global should */ +'use strict'; + +require('should'); + +describe('API3 SEARCH', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + , opTools = require('../lib/api3/shared/operationTools') + ; + + self.docs = testConst.SAMPLE_ENTRIES; + + self.timeout(15000); + + + /** + * Get document detail for futher processing + */ + self.get = function get (identifier, done) { + self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + .expect(200) + .end((err, res) => { + should.not.exist(err); + done(res.body); + }); + }; + + + /** + * Create given document in a promise + */ + self.create = (doc) => new Promise((resolve) => { + doc.identifier = opTools.calculateIdentifier(doc); + self.instance.post(`${self.url}?token=${self.token.all}`) + .send(doc) + .end((err) => { + should.not.exist(err); + self.get(doc.identifier, resolve); + }); + }); + + + before(async () => { + self.testStarted = new Date(); + self.instance = await instance.create({}); + + self.app = self.instance.app; + self.env = self.instance.env; + self.url = '/api/v3/entries'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + self.urlToken = `${self.url}?token=${self.token.read}`; + self.urlTest = `${self.urlToken}&srvModified$gte=${self.testStarted.getTime()}`; + + const promises = testConst.SAMPLE_ENTRIES.map(doc => self.create(doc)); + self.docs = await Promise.all(promises); + }); + + + after(() => { + self.instance.server.close(); + }); + + + it('should require authentication', async () => { + let res = await self.instance.get(self.url) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Missing or bad access token or JWT'); + }); + + + it('should not found not existing collection', async () => { + let res = await self.instance.get(`/api/v3/NOT_EXIST?token=${self.url}`) + .send(self.validDoc) + .expect(404); + + res.body.should.be.empty(); + }); + + + it('should found at least 10 documents', async () => { + let res = await self.instance.get(self.urlToken) + .expect(200); + + res.body.length.should.be.aboveOrEqual(self.docs.length); + }); + + + it('should found at least 10 documents from test start', async () => { + let res = await self.instance.get(self.urlTest) + .expect(200); + + res.body.length.should.be.aboveOrEqual(self.docs.length); + }); + + + it('should reject invalid limit - not a number', async () => { + let res = await self.instance.get(`${self.urlToken}&limit=INVALID`) + .expect(400); + + res.body.status.should.be.equal(400); + res.body.message.should.be.equal('Parameter limit out of tolerance'); + }); + + + it('should reject invalid limit - negative number', async () => { + let res = await self.instance.get(`${self.urlToken}&limit=-1`) + .expect(400); + + res.body.status.should.be.equal(400); + res.body.message.should.be.equal('Parameter limit out of tolerance'); + }); + + + it('should reject invalid limit - zero', async () => { + let res = await self.instance.get(`${self.urlToken}&limit=0`) + .expect(400); + + res.body.status.should.be.equal(400); + res.body.message.should.be.equal('Parameter limit out of tolerance'); + }); + + + it('should accept valid limit', async () => { + let res = await self.instance.get(`${self.urlToken}&limit=3`) + .expect(200); + + res.body.length.should.be.equal(3); + }); + + + it('should reject invalid skip - not a number', async () => { + let res = await self.instance.get(`${self.urlToken}&skip=INVALID`) + .expect(400); + + res.body.status.should.be.equal(400); + res.body.message.should.be.equal('Parameter skip out of tolerance'); + }); + + + it('should reject invalid skip - negative number', async () => { + let res = await self.instance.get(`${self.urlToken}&skip=-5`) + .expect(400); + + res.body.status.should.be.equal(400); + res.body.message.should.be.equal('Parameter skip out of tolerance'); + }); + + + it('should reject both sort and sort$desc', async () => { + let res = await self.instance.get(`${self.urlToken}&sort=date&sort$desc=created_at`) + .expect(400); + + res.body.status.should.be.equal(400); + res.body.message.should.be.equal('Parameters sort and sort_desc cannot be combined'); + }); + + + it('should sort well by date field', async () => { + let res = await self.instance.get(`${self.urlTest}&sort=date`) + .expect(200); + + const ascending = res.body; + const length = ascending.length; + length.should.be.aboveOrEqual(self.docs.length); + + res = await self.instance.get(`${self.urlTest}&sort$desc=date`) + .expect(200); + + const descending = res.body; + descending.length.should.equal(length); + + for (let i in ascending) { + ascending[i].should.eql(descending[length - i - 1]); + + if (i > 0) { + ascending[i - 1].date.should.be.lessThanOrEqual(ascending[i].date); + } + } + }); + + + it('should skip documents', async () => { + let res = await self.instance.get(`${self.urlToken}&sort=date&limit=8`) + .expect(200); + + const fullDocs = res.body; + fullDocs.length.should.be.equal(8); + + res = await self.instance.get(`${self.urlToken}&sort=date&skip=3&limit=5`) + .expect(200); + + const skipDocs = res.body; + skipDocs.length.should.be.equal(5); + + for (let i = 0; i < 3; i++) { + skipDocs[i].should.be.eql(fullDocs[i + 3]); + } + }); + + + it('should project selected fields', async () => { + let res = await self.instance.get(`${self.urlToken}&fields=date,app,subject`) + .expect(200); + + res.body.forEach(doc => { + const docFields = Object.getOwnPropertyNames(doc); + docFields.sort().should.be.eql(['app', 'date', 'subject']); + }); + }); + + + it('should project all fields', async () => { + let res = await self.instance.get(`${self.urlToken}&fields=_all`) + .expect(200); + + res.body.forEach(doc => { + Object.getOwnPropertyNames(doc).length.should.be.aboveOrEqual(10); + Object.prototype.hasOwnProperty.call(doc, '_id').should.not.be.true(); + Object.prototype.hasOwnProperty.call(doc, 'identifier').should.be.true(); + Object.prototype.hasOwnProperty.call(doc, 'srvModified').should.be.true(); + Object.prototype.hasOwnProperty.call(doc, 'srvCreated').should.be.true(); + }); + }); + + + it('should not exceed the limit of docs count', async () => { + const apiApp = self.instance.ctx.apiApp + , limitBackup = apiApp.get('API3_MAX_LIMIT'); + apiApp.set('API3_MAX_LIMIT', 5); + let res = await self.instance.get(`${self.urlToken}&limit=10`) + .expect(400); + + res.body.status.should.be.equal(400); + res.body.message.should.be.equal('Parameter limit out of tolerance'); + apiApp.set('API3_MAX_LIMIT', limitBackup); + }); + + + it('should respect the ceiling (hard) limit of docs', async () => { + const apiApp = self.instance.ctx.apiApp + , limitBackup = apiApp.get('API3_MAX_LIMIT'); + apiApp.set('API3_MAX_LIMIT', 5); + let res = await self.instance.get(`${self.urlToken}`) + .expect(200); + + res.body.length.should.be.equal(5); + apiApp.set('API3_MAX_LIMIT', limitBackup); + }); + +}); + diff --git a/tests/api3.security.test.js b/tests/api3.security.test.js new file mode 100644 index 00000000000..4cdc8e22b21 --- /dev/null +++ b/tests/api3.security.test.js @@ -0,0 +1,189 @@ +/* eslint require-atomic-updates: 0 */ +'use strict'; + +const request = require('supertest') + , apiConst = require('../lib/api3/const.json') + , semver = require('semver') + , moment = require('moment') + ; +require('should'); + +describe('Security of REST API3', function() { + const self = this + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + ; + + this.timeout(30000); + + + before(async () => { + self.http = await instance.create({ useHttps: false }); + self.https = await instance.create({ }); + + let authResult = await authSubject(self.https.ctx.authorization.storage); + self.subject = authResult.subject; + self.token = authResult.token; + }); + + + after(() => { + self.http.server.close(); + self.https.server.close(); + }); + + + it('should require HTTPS', async () => { + if (semver.gte(process.version, '10.0.0')) { + let res = await request(self.http.baseUrl) // hangs on 8.x.x (no reason why) + .get('/api/v3/test') + .expect(403); + + res.body.status.should.equal(403); + res.body.message.should.equal(apiConst.MSG.HTTP_403_NOT_USING_HTTPS); + } + }); + + + it('should require Date header', async () => { + let res = await request(self.https.baseUrl) + .get('/api/v3/test') + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal(apiConst.MSG.HTTP_401_MISSING_DATE); + }); + + + it('should validate Date header syntax', async () => { + let res = await request(self.https.baseUrl) + .get('/api/v3/test') + .set('Date', 'invalid date header') + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal(apiConst.MSG.HTTP_401_BAD_DATE); + }); + + + it('should reject Date header out of tolerance', async () => { + const oldDate = new Date((new Date() * 1) - 2 * 3600 * 1000) + , futureDate = new Date((new Date() * 1) + 2 * 3600 * 1000); + + let res = await request(self.https.baseUrl) + .get('/api/v3/test') + .set('Date', oldDate.toUTCString()) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal(apiConst.MSG.HTTP_401_DATE_OUT_OF_TOLERANCE); + + res = await request(self.https.baseUrl) + .get('/api/v3/test') + .set('Date',futureDate.toUTCString()) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal(apiConst.MSG.HTTP_401_DATE_OUT_OF_TOLERANCE); + }); + + + it('should reject invalid now ABC', async () => { + let res = await request(self.https.baseUrl) + .get(`/api/v3/test?now=ABC`) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Bad Date header'); + }); + + + it('should reject invalid now -1', async () => { + let res = await request(self.https.baseUrl) + .get(`/api/v3/test?now=-1`) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Bad Date header'); + }); + + + it('should reject invalid now - illegal format', async () => { + let res = await request(self.https.baseUrl) + .get(`/api/v3/test?now=2019-20-60T50:90:90`) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Bad Date header'); + }); + + + it('should require token', async () => { + let res = await request(self.https.baseUrl) + .get('/api/v3/test') + .set('Date', new Date().toUTCString()) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal(apiConst.MSG.HTTP_401_MISSING_OR_BAD_TOKEN); + }); + + + it('should require valid token', async () => { + let res = await request(self.https.baseUrl) + .get('/api/v3/test?token=invalid_token') + .set('Date', new Date().toUTCString()) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal(apiConst.MSG.HTTP_401_MISSING_OR_BAD_TOKEN); + }); + + + it('should deny subject denied', async () => { + let res = await request(self.https.baseUrl) + .get('/api/v3/test?token=' + self.subject.denied.accessToken) + .set('Date', new Date().toUTCString()) + .expect(403); + + res.body.status.should.equal(403); + res.body.message.should.equal(apiConst.MSG.HTTP_403_MISSING_PERMISSION.replace('{0}', 'api:entries:read')); + }); + + + it('should allow subject with read permission', async () => { + await request(self.https.baseUrl) + .get('/api/v3/test?token=' + self.token.read) + .set('Date', new Date().toUTCString()) + .expect(200); + }); + + + it('should accept valid now - epoch in ms', async () => { + await request(self.https.baseUrl) + .get(`/api/v3/test?token=${self.token.read}&now=${moment().valueOf()}`) + .expect(200); + }); + + + it('should accept valid now - epoch in seconds', async () => { + await request(self.https.baseUrl) + .get(`/api/v3/test?token=${self.token.read}&now=${moment().unix()}`) + .expect(200); + }); + + + it('should accept valid now - ISO 8601', async () => { + await request(self.https.baseUrl) + .get(`/api/v3/test?token=${self.token.read}&now=${moment().toISOString()}`) + .expect(200); + }); + + + it('should accept valid now - RFC 2822', async () => { + await request(self.https.baseUrl) + .get(`/api/v3/test?token=${self.token.read}&now=${moment().utc().format('ddd, DD MMM YYYY HH:mm:ss [GMT]')}`) + .expect(200); + }); + +}); \ No newline at end of file diff --git a/tests/api3.socket.test.js b/tests/api3.socket.test.js new file mode 100644 index 00000000000..5c2a5cf6461 --- /dev/null +++ b/tests/api3.socket.test.js @@ -0,0 +1,178 @@ +/* eslint require-atomic-updates: 0 */ +/* global should */ +'use strict'; + +require('should'); + +describe('Socket.IO in REST API3', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , apiConst = require('../lib/api3/const.json') + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + , utils = require('./fixtures/api3/utils') + ; + + self.identifier = utils.randomString('32', 'aA#'); // let's have a brand new identifier for your testing document + + self.docOriginal = { + identifier: self.identifier, + eventType: 'Correction Bolus', + insulin: 1, + date: (new Date()).getTime(), + app: testConst.TEST_APP + }; + + this.timeout(30000); + + before(async () => { + self.instance = await instance.create({ + storageSocket: true + }); + + self.app = self.instance.app; + self.env = self.instance.env; + self.colName = 'treatments'; + self.urlCol = `/api/v3/${self.colName}`; + self.urlResource = self.urlCol + '/' + self.identifier; + self.urlHistory = self.urlCol + '/history'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + self.socket = self.instance.clientSocket; + }); + + + after(() => { + if(self.instance && self.instance.clientSocket && self.instance.clientSocket.connected) { + self.instance.clientSocket.disconnect(); + } + self.instance.server.close(); + }); + + + it('should not subscribe without accessToken', done => { + self.socket.emit('subscribe', { }, function (data) { + data.success.should.not.equal(true); + data.message.should.equal(apiConst.MSG.SOCKET_MISSING_OR_BAD_ACCESS_TOKEN); + done(); + }); + }); + + + it('should not subscribe by invalid accessToken', done => { + self.socket.emit('subscribe', { accessToken: 'INVALID' }, function (data) { + data.success.should.not.equal(true); + data.message.should.equal(apiConst.MSG.SOCKET_MISSING_OR_BAD_ACCESS_TOKEN); + done(); + }); + }); + + + it('should not subscribe by subject with no rights', done => { + self.socket.emit('subscribe', { accessToken: self.token.denied }, function (data) { + data.success.should.not.equal(true); + data.message.should.equal(apiConst.MSG.SOCKET_UNAUTHORIZED_TO_ANY); + done(); + }); + }); + + + it('should subscribe by valid accessToken', done => { + const cols = ['entries', 'treatments']; + + self.socket.emit('subscribe', { + accessToken: self.token.all, + collections: cols + }, function (data) { + data.success.should.equal(true); + should(data.collections.sort()).be.eql(cols); + done(); + }); + }); + + + it('should emit create event on CREATE', done => { + + self.socket.once('create', (event) => { + event.colName.should.equal(self.colName); + event.doc.should.containEql(self.docOriginal); + delete event.doc.subject; + self.docActual = event.doc; + done(); + }); + + self.instance.post(`${self.urlCol}?token=${self.token.create}`) + .send(self.docOriginal) + .expect(201) + .end((err) => { + should.not.exist(err); + }); + }); + + + it('should emit update event on UPDATE', done => { + + self.docActual.insulin = 0.5; + + self.socket.once('update', (event) => { + delete self.docActual.srvModified; + event.colName.should.equal(self.colName); + event.doc.should.containEql(self.docActual); + delete event.doc.subject; + self.docActual = event.doc; + done(); + }); + + self.instance.put(`${self.urlResource}?token=${self.token.update}`) + .send(self.docActual) + .expect(204) + .end((err) => { + should.not.exist(err); + self.docActual.subject = self.subject.apiUpdate.name; + }); + }); + + + it('should emit update event on PATCH', done => { + + self.docActual.carbs = 5; + self.docActual.insulin = 0.4; + + self.socket.once('update', (event) => { + delete self.docActual.srvModified; + event.colName.should.equal(self.colName); + event.doc.should.containEql(self.docActual); + delete event.doc.subject; + self.docActual = event.doc; + done(); + }); + + self.instance.patch(`${self.urlResource}?token=${self.token.update}`) + .send({ 'carbs': self.docActual.carbs, 'insulin': self.docActual.insulin }) + .expect(204) + .end((err) => { + should.not.exist(err); + }); + }); + + + it('should emit delete event on DELETE', done => { + + self.socket.once('delete', (event) => { + event.colName.should.equal(self.colName); + event.identifier.should.equal(self.identifier); + done(); + }); + + self.instance.delete(`${self.urlResource}?token=${self.token.delete}`) + .expect(204) + .end((err) => { + should.not.exist(err); + }); + }); + +}); + diff --git a/tests/api3.update.test.js b/tests/api3.update.test.js new file mode 100644 index 00000000000..403aadb022e --- /dev/null +++ b/tests/api3.update.test.js @@ -0,0 +1,289 @@ +/* eslint require-atomic-updates: 0 */ +/* global should */ +'use strict'; + +require('should'); + +describe('API3 UPDATE', function() { + const self = this + , testConst = require('./fixtures/api3/const.json') + , instance = require('./fixtures/api3/instance') + , authSubject = require('./fixtures/api3/authSubject') + , utils = require('./fixtures/api3/utils') + ; + + self.validDoc = { + identifier: utils.randomString('32', 'aA#'), + date: (new Date()).getTime(), + utcOffset: -180, + app: testConst.TEST_APP, + eventType: 'Correction Bolus', + insulin: 0.3 + }; + + self.timeout(15000); + + + /** + * Get document detail for futher processing + */ + self.get = async function get (identifier) { + let res = await self.instance.get(`${self.url}/${identifier}?token=${self.token.read}`) + .expect(200); + + return res.body; + }; + + + before(async () => { + self.instance = await instance.create({}); + + self.app = self.instance.app; + self.env = self.instance.env; + self.url = '/api/v3/treatments'; + + let authResult = await authSubject(self.instance.ctx.authorization.storage); + + self.subject = authResult.subject; + self.token = authResult.token; + self.urlToken = `${self.url}/${self.validDoc.identifier}?token=${self.token.update}` + }); + + + after(() => { + self.instance.server.close(); + }); + + + it('should require authentication', async () => { + let res = await self.instance.put(`${self.url}/FAKE_IDENTIFIER`) + .expect(401); + + res.body.status.should.equal(401); + res.body.message.should.equal('Missing or bad access token or JWT'); + }); + + + it('should not found not existing collection', async () => { + let res = await self.instance.put(`/api/v3/NOT_EXIST?token=${self.url}`) + .send(self.validDoc) + .expect(404); + + res.body.should.be.empty(); + }); + + + it('should require update permission for upsert', async () => { + let res = await self.instance.put(`${self.url}/${self.validDoc.identifier}?token=${self.token.update}`) + .send(self.validDoc) + .expect(403); + + res.body.status.should.equal(403); + res.body.message.should.equal('Missing permission api:treatments:create'); + }); + + + it('should upsert not existing document', async () => { + let res = await self.instance.put(`${self.url}/${self.validDoc.identifier}?token=${self.token.all}`) + .send(self.validDoc) + .expect(201); + + res.body.should.be.empty(); + + const lastModified = new Date(res.headers['last-modified']).getTime(); // Last-Modified has trimmed milliseconds + + let body = await self.get(self.validDoc.identifier); + body.should.containEql(self.validDoc); + should.not.exist(body.modifiedBy); + + const ms = body.srvModified % 1000; + (body.srvModified - ms).should.equal(lastModified); + (body.srvCreated - ms).should.equal(lastModified); + body.subject.should.equal(self.subject.apiAll.name); + }); + + + it('should update the document', async () => { + self.validDoc.carbs = 10; + delete self.validDoc.insulin; + + let res = await self.instance.put(self.urlToken) + .send(self.validDoc) + .expect(204); + + res.body.should.be.empty(); + + const lastModified = new Date(res.headers['last-modified']).getTime(); // Last-Modified has trimmed milliseconds + + let body = await self.get(self.validDoc.identifier); + body.should.containEql(self.validDoc); + should.not.exist(body.insulin); + should.not.exist(body.modifiedBy); + + const ms = body.srvModified % 1000; + (body.srvModified - ms).should.equal(lastModified); + body.subject.should.equal(self.subject.apiUpdate.name); + }); + + + it('should update unmodified document since', async () => { + const doc = Object.assign({}, self.validDoc, { + carbs: 11 + }); + let res = await self.instance.put(self.urlToken) + .set('If-Unmodified-Since', new Date(new Date().getTime() + 1000).toUTCString()) + .send(doc) + .expect(204); + + res.body.should.be.empty(); + + let body = await self.get(self.validDoc.identifier); + body.should.containEql(doc); + }); + + + it('should not update document modified since', async () => { + const doc = Object.assign({}, self.validDoc, { + carbs: 12 + }); + let body = await self.get(doc.identifier); + self.validDoc = body; + + let res = await self.instance.put(self.urlToken) + .set('If-Unmodified-Since', new Date(new Date(body.srvModified).getTime() - 1000).toUTCString()) + .send(doc) + .expect(412); + + res.body.should.be.empty(); + + body = await self.get(doc.identifier); + body.should.eql(self.validDoc); + }); + + + it('should reject date alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { date: self.validDoc.date + 10000 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field date cannot be modified by the client'); + }); + + + it('should reject utcOffset alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { utcOffset: self.utcOffset - 120 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field utcOffset cannot be modified by the client'); + }); + + + it('should reject eventType alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { eventType: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field eventType cannot be modified by the client'); + }); + + + it('should reject device alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { device: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field device cannot be modified by the client'); + }); + + + it('should reject app alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { app: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field app cannot be modified by the client'); + }); + + + it('should reject srvCreated alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { srvCreated: self.validDoc.date - 10000 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field srvCreated cannot be modified by the client'); + }); + + + it('should reject subject alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { subject: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field subject cannot be modified by the client'); + }); + + + it('should reject srvModified alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { srvModified: self.validDoc.date - 100000 })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field srvModified cannot be modified by the client'); + }); + + + it('should reject modifiedBy alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { modifiedBy: 'MODIFIED' })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field modifiedBy cannot be modified by the client'); + }); + + + it('should reject isValid alteration', async () => { + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { isValid: false })) + .expect(400); + + res.body.status.should.equal(400); + res.body.message.should.equal('Field isValid cannot be modified by the client'); + }); + + + it('should ignore identifier alteration in body', async () => { + self.validDoc = await self.get(self.validDoc.identifier); + + let res = await self.instance.put(self.urlToken) + .send(Object.assign({}, self.validDoc, { identifier: 'MODIFIED' })) + .expect(204); + + res.body.should.be.empty(); + }); + + + it('should not update deleted document', async () => { + let res = await self.instance.delete(`${self.url}/${self.validDoc.identifier}?token=${self.token.delete}`) + .expect(204); + + res.body.should.be.empty(); + + res = await self.instance.put(self.urlToken) + .send(self.validDoc) + .expect(410); + + res.body.should.be.empty(); + }); + +}); + diff --git a/tests/ar2.test.js b/tests/ar2.test.js index 9dbf6de14cd..01f4f3d41a1 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -147,18 +147,18 @@ describe('ar2', function ( ) { done(); }); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var now = Date.now(); var before = now - FIVE_MINS; ctx.ddata.sgvs = [{mgdl: 100, mills: before}, {mgdl: 105, mills: now}]; var sbx = prepareSandbox(); - ar2.alexa.intentHandlers.length.should.equal(1); + ar2.virtAsst.intentHandlers.length.should.equal(1); - ar2.alexa.intentHandlers[0].intentHandler(function next(title, response) { + ar2.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { title.should.equal('AR2 Forecast'); - response.should.equal('You are expected to be between 109 and 120 over the in 30 minutes'); + response.should.equal('According to the AR2 forecast you are expected to be between 109 and 120 over the next in 30 minutes'); done(); }, [], sbx); }); diff --git a/tests/basalprofileplugin.test.js b/tests/basalprofileplugin.test.js index 0bcfd3bc268..fa97f84274e 100644 --- a/tests/basalprofileplugin.test.js +++ b/tests/basalprofileplugin.test.js @@ -77,7 +77,7 @@ describe('basalprofile', function ( ) { }); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var data = {}; var ctx = { @@ -92,14 +92,14 @@ describe('basalprofile', function ( ) { var sbx = sandbox.clientInit(ctx, time, data); sbx.data.profile = profile; - basal.alexa.intentHandlers.length.should.equal(1); - basal.alexa.rollupHandlers.length.should.equal(1); + basal.virtAsst.intentHandlers.length.should.equal(1); + basal.virtAsst.rollupHandlers.length.should.equal(1); - basal.alexa.intentHandlers[0].intentHandler(function next(title, response) { + basal.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { title.should.equal('Current Basal'); response.should.equal('Your current basal is 0.175 units per hour'); - basal.alexa.rollupHandlers[0].rollupHandler([], sbx, function callback (err, response) { + basal.virtAsst.rollupHandlers[0].rollupHandler([], sbx, function callback (err, response) { should.not.exist(err); response.results.should.equal('Your current basal is 0.175 units per hour'); response.priority.should.equal(1); diff --git a/tests/bgnow.test.js b/tests/bgnow.test.js index 819f3dafbfc..c87e513c48d 100644 --- a/tests/bgnow.test.js +++ b/tests/bgnow.test.js @@ -9,6 +9,7 @@ var SIX_MINS = 360000; describe('BG Now', function ( ) { var ctx = { language: require('../lib/language')() + , settings: require('../lib/settings')() }; ctx.levels = require('../lib/levels'); diff --git a/tests/bridge.test.js b/tests/bridge.test.js index 99c1587fab4..66b69f64c3a 100644 --- a/tests/bridge.test.js +++ b/tests/bridge.test.js @@ -10,6 +10,7 @@ describe('bridge', function ( ) { bridge: { userName: 'nightscout' , password: 'wearenotwaiting' + , interval: 60000 } } }; @@ -27,6 +28,7 @@ describe('bridge', function ( ) { opts.login.accountName.should.equal('nightscout'); opts.login.password.should.equal('wearenotwaiting'); + opts.interval.should.equal(60000); }); it('store entries from share', function (done) { @@ -39,4 +41,43 @@ describe('bridge', function ( ) { bridge.bridged(mockEntries)(null); }); + it('set too low bridge interval option from env', function () { + var tooLowInterval = { + extendedSettings: { + bridge: { interval: 900 } + } + }; + + var opts = bridge.options(tooLowInterval); + should.exist(opts); + + opts.interval.should.equal(150000); + }); + + it('set too high bridge interval option from env', function () { + var tooHighInterval = { + extendedSettings: { + bridge: { interval: 500000 } + } + }; + + var opts = bridge.options(tooHighInterval); + should.exist(opts); + + opts.interval.should.equal(150000); + }); + + it('set no bridge interval option from env', function () { + var noInterval = { + extendedSettings: { + bridge: { } + } + }; + + var opts = bridge.options(noInterval); + should.exist(opts); + + opts.interval.should.equal(150000); + }); + }); diff --git a/tests/careportal.test.js b/tests/careportal.test.js index 782bc4fa566..36f48d3a5a4 100644 --- a/tests/careportal.test.js +++ b/tests/careportal.test.js @@ -49,7 +49,7 @@ describe('client', function ( ) { client.init(); - client.dataUpdate(nowData); + client.dataUpdate(nowData, true); client.careportal.prepareEvents(); diff --git a/tests/client.renderer.test.js b/tests/client.renderer.test.js index 569691cd717..ca81e7d99e8 100644 --- a/tests/client.renderer.test.js +++ b/tests/client.renderer.test.js @@ -54,6 +54,10 @@ describe('renderer', () => { } } , futureOpacity: (millsDifference) => { return 1; } + , createAdjustedRange: () => { return [ + { getTime: () => { return extent.times[0]}}, + { getTime: () => { return extent.times[1]}} + ] } } , latestSGV: { mills: 120 } }; diff --git a/tests/cob.test.js b/tests/cob.test.js index dbbecda0b67..54fbcb6c50d 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -97,7 +97,7 @@ describe('COB', function ( ) { }); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var data = { treatments: [{ carbs: '8' @@ -110,9 +110,9 @@ describe('COB', function ( ) { var sbx = sandbox.clientInit(ctx, Date.now(), data); cob.setProperties(sbx); - cob.alexa.intentHandlers.length.should.equal(1); + cob.virtAsst.intentHandlers.length.should.equal(1); - cob.alexa.intentHandlers[0].intentHandler(function next(title, response) { + cob.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { title.should.equal('Current COB'); response.should.equal('You have 8 carbohydrates on board'); done(); diff --git a/tests/ddata.test.js b/tests/ddata.test.js index f3757348c53..ceb163b7c4f 100644 --- a/tests/ddata.test.js +++ b/tests/ddata.test.js @@ -41,19 +41,6 @@ describe('ddata', function ( ) { done( ); }); - it('has #split( )', function (done) { - var date = new Date( ); - var time = date.getTime( ); - var cutoff = 1000 * 60 * 5; - var max = 1000 * 60 * 60 * 24 * 2; - var pieces = ctx.ddata.splitRecent(time, cutoff, max); - should.exist(pieces); - should.exist(pieces.first); - should.exist(pieces.rest); - - done( ); - }); - // TODO: ensure partition function gets called via: // Properties // * ddata.devicestatus diff --git a/tests/env.test.js b/tests/env.test.js index 12d9f5793bd..90313dfd39f 100644 --- a/tests/env.test.js +++ b/tests/env.test.js @@ -3,53 +3,69 @@ require('should'); describe('env', function ( ) { - it('show the right plugins', function () { + it( 'show the right plugins', function () { process.env.SHOW_PLUGINS = 'iob'; process.env.ENABLE = 'iob cob'; - var env = require('../env')(); + var env = require( '../env' )(); var showPlugins = env.settings.showPlugins; - showPlugins.should.containEql('iob'); - showPlugins.should.containEql('delta'); - showPlugins.should.containEql('direction'); - showPlugins.should.containEql('upbat'); + showPlugins.should.containEql( 'iob' ); + showPlugins.should.containEql( 'delta' ); + showPlugins.should.containEql( 'direction' ); + showPlugins.should.containEql( 'upbat' ); delete process.env.SHOW_PLUGINS; delete process.env.ENABLE; - }); + } ); - it('get extended settings', function () { + it( 'get extended settings', function () { process.env.ENABLE = 'scaryplugin'; process.env.SCARYPLUGIN_DO_THING = 'yes'; - var env = require('../env')(); - env.settings.isEnabled('scaryplugin').should.equal(true); + var env = require( '../env' )(); + env.settings.isEnabled( 'scaryplugin' ).should.equal( true ); //Note the camelCase - env.extendedSettings.scaryplugin.doThing.should.equal('yes'); + env.extendedSettings.scaryplugin.doThing.should.equal( 'yes' ); delete process.env.ENABLE; delete process.env.SCARYPLUGIN_DO_THING; - }); + } ); - it('add pushover to enable if one of the env vars is set', function () { + it( 'add pushover to enable if one of the env vars is set', function () { process.env.PUSHOVER_API_TOKEN = 'abc12345'; - var env = require('../env')(); - env.settings.enable.should.containEql('pushover'); - env.extendedSettings.pushover.apiToken.should.equal('abc12345'); + var env = require( '../env' )(); + env.settings.enable.should.containEql( 'pushover' ); + env.extendedSettings.pushover.apiToken.should.equal( 'abc12345' ); delete process.env.PUSHOVER_API_TOKEN; - }); + } ); - it('add pushover to enable if one of the weird azure env vars is set', function () { + it( 'add pushover to enable if one of the weird azure env vars is set', function () { process.env.CUSTOMCONNSTR_PUSHOVER_API_TOKEN = 'abc12345'; - var env = require('../env')(); - env.settings.enable.should.containEql('pushover'); - env.extendedSettings.pushover.apiToken.should.equal('abc12345'); + var env = require( '../env' )(); + env.settings.enable.should.containEql( 'pushover' ); + env.extendedSettings.pushover.apiToken.should.equal( 'abc12345' ); delete process.env.PUSHOVER_API_TOKEN; - }); + } ); -}); + it( 'readENVTruthy ', function () { + process.env.INSECURE_USE_HTTP = 'true'; + var env = require( '../env' )(); + env.insecureUseHttp.should.be.true(); + process.env.INSECURE_USE_HTTP = 'false'; + env = require( '../env' )(); + env.insecureUseHttp.should.be.false(); + process.env.INSECURE_USE_HTTP = 'not set ok, so use default value false'; + env = require( '../env' )(); + env.insecureUseHttp.should.be.false(); + delete process.env.INSECURE_USE_HTTP; // unset INSECURE_USE_HTTP + process.env.SECURE_HSTS_HEADER = 'true'; + env = require( '../env' )(); + env.insecureUseHttp.should.be.false(); // not defined should be false + env.secureHstsHeader.should.be.true(); + }); +}) diff --git a/tests/fail.test.js b/tests/fail.test.js new file mode 100644 index 00000000000..eefda445b3d --- /dev/null +++ b/tests/fail.test.js @@ -0,0 +1,14 @@ +'use strict'; + +require('should'); + +// This test is included just so we have an easy to template to intentionally cause +// builds to fail + +describe('fail', function ( ) { + + it('should not fail', function () { + true.should.equal(true); + }); + +}); diff --git a/tests/fixtures/api/instance.js b/tests/fixtures/api/instance.js new file mode 100644 index 00000000000..ed5b28474c9 --- /dev/null +++ b/tests/fixtures/api/instance.js @@ -0,0 +1,98 @@ +'use strict'; + +const fs = require('fs') + , path = require('path') + , language = require('../../../lib/language')() + , apiRoot = require('../../../lib/api/root') + , http = require('http') + , https = require('https') + ; + +function configure () { + const self = { }; + + self.prepareEnv = function prepareEnv({ apiSecret, useHttps, authDefaultRoles, enable }) { + + if (useHttps) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + } + else { + process.env.INSECURE_USE_HTTP = true; + } + process.env.API_SECRET = apiSecret; + + process.env.HOSTNAME = 'localhost'; + const env = require('../../../env')(); + + if (useHttps) { + env.ssl = { + key: fs.readFileSync(path.join(__dirname, '../api3/localhost.key')), + cert: fs.readFileSync(path.join(__dirname, '../api3/localhost.crt')) + }; + } + + env.settings.authDefaultRoles = authDefaultRoles; + env.settings.enable = enable; + + return env; + }; + + + /* + * Create new web server instance for testing purposes + */ + self.create = function createHttpServer ({ + apiSecret = 'this is my long pass phrase', + useHttps = true, + authDefaultRoles = '', + enable = ['careportal', 'api'] + }) { + + return new Promise(function (resolve, reject) { + + try { + let instance = { }, + hasBooted = false + ; + + instance.env = self.prepareEnv({ apiSecret, useHttps, authDefaultRoles, enable }); + + self.wares = require('../../../lib/middleware/')(instance.env); + instance.app = require('express')(); + instance.app.enable('api'); + + require('../../../lib/server/bootevent')(instance.env, language).boot(function booted (ctx) { + instance.ctx = ctx; + instance.ctx.ddata = require('../../../lib/data/ddata')(); + instance.ctx.apiRootApp = apiRoot(instance.env, ctx); + + instance.app.use('/api', instance.ctx.apiRootApp); + + const transport = useHttps ? https : http; + + instance.server = transport.createServer(instance.env.ssl || { }, instance.app).listen(0); + instance.env.PORT = instance.server.address().port; + + instance.baseUrl = `${useHttps ? 'https' : 'http'}://${instance.env.HOSTNAME}:${instance.env.PORT}`; + + console.log(`Started ${useHttps ? 'SSL' : 'HTTP'} instance on ${instance.baseUrl}`); + hasBooted = true; + resolve(instance); + }); + + setTimeout(function watchDog() { + if (!hasBooted) + reject('timeout'); + }, 30000); + + } catch (err) { + reject(err); + } + }); + }; + + + return self; +} + +module.exports = configure(); \ No newline at end of file diff --git a/tests/fixtures/api3/authSubject.js b/tests/fixtures/api3/authSubject.js new file mode 100644 index 00000000000..6036103b0e5 --- /dev/null +++ b/tests/fixtures/api3/authSubject.js @@ -0,0 +1,94 @@ +'use strict'; + +const _ = require('lodash'); + +function createRole (authStorage, name, permissions) { + + return new Promise((resolve, reject) => { + + let role = _.find(authStorage.roles, { name }); + + if (role) { + resolve(role); + } + else { + authStorage.createRole({ + "name": name, + "permissions": permissions, + "notes": "" + }, function afterCreate (err) { + + if (err) + reject(err); + + role = _.find(authStorage.roles, { name }); + resolve(role); + }); + } + }); +} + + +function createTestSubject (authStorage, subjectName, roles) { + + return new Promise((resolve, reject) => { + + const subjectDbName = 'test-' + subjectName; + let subject = _.find(authStorage.subjects, { name: subjectDbName }); + + if (subject) { + resolve(subject); + } + else { + authStorage.createSubject({ + "name": subjectDbName, + "roles": roles, + "notes": "" + }, function afterCreate (err) { + + if (err) + reject(err); + + subject = _.find(authStorage.subjects, { name: subjectDbName }); + resolve(subject); + }); + } + }); +} + + +async function authSubject (authStorage) { + + await createRole(authStorage, 'apiAll', 'api:*:*'); + await createRole(authStorage, 'apiAdmin', 'api:*:admin'); + await createRole(authStorage, 'apiCreate', 'api:*:create'); + await createRole(authStorage, 'apiRead', 'api:*:read'); + await createRole(authStorage, 'apiUpdate', 'api:*:update'); + await createRole(authStorage, 'apiDelete', 'api:*:delete'); + + const subject = { + apiAll: await createTestSubject(authStorage, 'apiAll', ['apiAll']), + apiAdmin: await createTestSubject(authStorage, 'apiAdmin', ['apiAdmin']), + apiCreate: await createTestSubject(authStorage, 'apiCreate', ['apiCreate']), + apiRead: await createTestSubject(authStorage, 'apiRead', ['apiRead']), + apiUpdate: await createTestSubject(authStorage, 'apiUpdate', ['apiUpdate']), + apiDelete: await createTestSubject(authStorage, 'apiDelete', ['apiDelete']), + admin: await createTestSubject(authStorage, 'admin', ['admin']), + readable: await createTestSubject(authStorage, 'readable', ['readable']), + denied: await createTestSubject(authStorage, 'denied', ['denied']) + }; + + const token = { + all: subject.apiAll.accessToken, + admin: subject.apiAdmin.accessToken, + create: subject.apiCreate.accessToken, + read: subject.apiRead.accessToken, + update: subject.apiUpdate.accessToken, + delete: subject.apiDelete.accessToken, + denied: subject.denied.accessToken + }; + + return {subject, token}; +} + +module.exports = authSubject; \ No newline at end of file diff --git a/tests/fixtures/api3/const.json b/tests/fixtures/api3/const.json new file mode 100644 index 00000000000..a0acf37cfee --- /dev/null +++ b/tests/fixtures/api3/const.json @@ -0,0 +1,138 @@ +{ + "YEAR_2019": 1546304400000, + "YEAR_2050": 2524611600000, + "TEST_APP": "cgm-remote-monitor.test", + "TEST_DEVICE": "Samsung XCover 4-123456735643809", + + "SAMPLE_ENTRIES": [ + { + "date": 1491717830000.0, + "device": "dexcom", + "direction": "FortyFiveUp", + "filtered": 167584, + "noise": 2, + "rssi": 183, + "sgv": 149, + "type": "sgv", + "unfiltered": 171584, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491718130000.0, + "device": "dexcom", + "direction": "FortyFiveUp", + "filtered": 170656, + "noise": 2, + "rssi": 181, + "sgv": 152, + "type": "sgv", + "unfiltered": 175776, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491718430000.0, + "device": "dexcom", + "direction": "Flat", + "filtered": 173536, + "noise": 2, + "rssi": 185, + "sgv": 155, + "type": "sgv", + "unfiltered": 180864, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491718730000.0, + "device": "dexcom", + "direction": "Flat", + "filtered": 177120, + "noise": 2, + "rssi": 186, + "sgv": 159, + "type": "sgv", + "unfiltered": 182080, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491719030000.0, + "device": "dexcom", + "direction": "Flat", + "filtered": 181088, + "noise": 2, + "rssi": 165, + "sgv": 163, + "type": "sgv", + "unfiltered": 186912, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491719330000.0, + "device": "dexcom", + "direction": "Flat", + "filtered": 184736, + "noise": 1, + "rssi": 162, + "sgv": 170, + "type": "sgv", + "unfiltered": 188512, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491719630000.0, + "device": "dexcom", + "direction": "Flat", + "filtered": 187776, + "noise": 1, + "rssi": 175, + "sgv": 175, + "type": "sgv", + "unfiltered": 192608, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491719930000.0, + "device": "dexcom", + "direction": "Flat", + "filtered": 190816, + "noise": 1, + "rssi": 181, + "sgv": 179, + "type": "sgv", + "unfiltered": 196640, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491720230000.0, + "device": "dexcom", + "direction": "Flat", + "filtered": 194016, + "noise": 1, + "rssi": 203, + "sgv": 181, + "type": "sgv", + "unfiltered": 199008, + "app": "cgm-remote-monitor.test" + }, + + { + "date": 1491720530000.0, + "device": "dexcom", + "direction": "Flat", + "filtered": 197536, + "noise": 1, + "rssi": 184, + "sgv": 186, + "type": "sgv", + "unfiltered": 203296, + "app": "cgm-remote-monitor.test" + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/api3/instance.js b/tests/fixtures/api3/instance.js new file mode 100644 index 00000000000..a7693ab3c40 --- /dev/null +++ b/tests/fixtures/api3/instance.js @@ -0,0 +1,163 @@ +'use strict'; + +var fs = require('fs') + , language = require('../../../lib/language')() + , api = require('../../../lib/api3/') + , http = require('http') + , https = require('https') + , request = require('supertest') + , websocket = require('../../../lib/server/websocket') + , io = require('socket.io-client') + ; + +function configure () { + const self = { }; + + self.prepareEnv = function prepareEnv({ apiSecret, useHttps, authDefaultRoles, enable }) { + + if (useHttps) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + } + else { + process.env.INSECURE_USE_HTTP = true; + } + process.env.API_SECRET = apiSecret; + + process.env.HOSTNAME = 'localhost'; + const env = require('../../../env')(); + + if (useHttps) { + env.ssl = { + key: fs.readFileSync(__dirname + '/localhost.key'), + cert: fs.readFileSync(__dirname + '/localhost.crt') + }; + } + + env.settings.authDefaultRoles = authDefaultRoles; + env.settings.enable = enable; + + return env; + }; + + + self.addSecuredOperations = function addSecuredOperations (instance) { + + instance.get = (url) => request(instance.baseUrl).get(url).set('Date', new Date().toUTCString()); + + instance.post = (url) => request(instance.baseUrl).post(url).set('Date', new Date().toUTCString()); + + instance.put = (url) => request(instance.baseUrl).put(url).set('Date', new Date().toUTCString()); + + instance.patch = (url) => request(instance.baseUrl).patch(url).set('Date', new Date().toUTCString()); + + instance.delete = (url) => request(instance.baseUrl).delete(url).set('Date', new Date().toUTCString()); + }; + + + self.bindSocket = function bindSocket (storageSocket, instance) { + + return new Promise(function (resolve, reject) { + if (!storageSocket) { + resolve(); + } + else { + let socket = io(`${instance.baseUrl}/storage`, { + origins:"*", + transports: ['websocket', 'flashsocket', 'polling'], + rejectUnauthorized: false + }); + + socket.on('connect', function () { + resolve(socket); + }); + socket.on('connect_error', function (error) { + console.error(error); + reject(error); + }); + } + }); + }; + + + self.unbindSocket = function unbindSocket (instance) { + if (instance.clientSocket.connected) { + instance.clientSocket.disconnect(); + } + }; + + /* + * Create new web server instance for testing purposes + */ + self.create = function createHttpServer ({ + apiSecret = 'this is my long pass phrase', + disableSecurity = false, + useHttps = true, + authDefaultRoles = '', + enable = ['careportal', 'api'], + storageSocket = null + }) { + + return new Promise(function (resolve, reject) { + + try { + let instance = { }, + hasBooted = false + ; + + instance.env = self.prepareEnv({ apiSecret, useHttps, authDefaultRoles, enable }); + + self.wares = require('../../../lib/middleware/')(instance.env); + instance.app = require('express')(); + instance.app.enable('api'); + + require('../../../lib/server/bootevent')(instance.env, language).boot(function booted (ctx) { + instance.ctx = ctx; + instance.ctx.ddata = require('../../../lib/data/ddata')(); + instance.ctx.apiApp = api(instance.env, ctx); + + if (disableSecurity) { + instance.ctx.apiApp.set('API3_SECURITY_ENABLE', false); + } + + instance.app.use('/api/v3', instance.ctx.apiApp); + + const transport = useHttps ? https : http; + + instance.server = transport.createServer(instance.env.ssl || { }, instance.app).listen(0); + instance.env.PORT = instance.server.address().port; + + instance.baseUrl = `${useHttps ? 'https' : 'http'}://${instance.env.HOSTNAME}:${instance.env.PORT}`; + + self.addSecuredOperations(instance); + + websocket(instance.env, instance.ctx, instance.server); + + self.bindSocket(storageSocket, instance) + .then((socket) => { + instance.clientSocket = socket; + + console.log(`Started ${useHttps ? 'SSL' : 'HTTP'} instance on ${instance.baseUrl}`); + hasBooted = true; + resolve(instance); + }) + .catch((reason) => { + console.error(reason); + reject(reason); + }); + }); + + setTimeout(function watchDog() { + if (!hasBooted) + reject('timeout'); + }, 30000); + + } catch (err) { + reject(err); + } + }); + }; + + return self; +} + +module.exports = configure(); \ No newline at end of file diff --git a/tests/fixtures/api3/localhost.crt b/tests/fixtures/api3/localhost.crt new file mode 100644 index 00000000000..21a2a39b0a4 --- /dev/null +++ b/tests/fixtures/api3/localhost.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgIJAIx0y57dTqDpMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAeFw0xOTAyMDQxOTM1MDhaFw0yOTAyMDExOTM1MDhaMBQx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKCeBaqAJU+nrzNUZMsD1jYQpmcw8+6tG69KQY2XmqMsaPupo2ArwUlYD3pm +F1HTf9Lkq8u07rlUyMaSSRYrY56vPrMWGSK5Elm4kF8DNS4b/55KwZC+YQM0ZuJK +wSM6WX4G7JwV936HKJAT+Ec+8Ofq3GQzA9+Z4x2zMwNGC8AghtPjsCk68ORCmr+5 +fdCdC1Rz9hE92Nmofi8e1hUTeZmFROx8hcYRhxYXLIWVxALc/t8yY3MZfsRuZXcP +/3PageAn0ecxhqlWBY23GDQx7OSEZxSEPgqxnAHQfQXIrPRjMkFNHeMM7HTvITAG +VCc99zEG3Jy5hatm+RAajdWBH4sCAwEAAaNQME4wHQYDVR0OBBYEFJJVZn5Y91O7 +JUKeHW4La8eseKKwMB8GA1UdIwQYMBaAFJJVZn5Y91O7JUKeHW4La8eseKKwMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAFOU19t9h6C1Hakkik/93kun +pwG7v8VvDPjKECR5KlNPKNZUOQaiMAVHgNwPWV8q+qvfydzIpDrTd/O5eOaOduLx +gDVDj078Q05j17RUC+ct5yQ6lPgEHlnkI0Zr/hgFyNC+mtK7oIm6BT8wSSRbv7AG +3wQzCA5UvW/BQ8rtNZSC42Jyr0BR0ZS9Fo3Gc4v/nZJlgkiBvU2gKVQ7VRKxybCn +0hDghVwTfBPq7PKmupLX82ktwhYpDJZXCsOVfq9mF6nbQ6b0MieXFD+7cBlEXb1e +3VgtVzKYyqh/Oex4HfMThzAJZSWa0E4FShr5XdTdIc3nB4Vgbsis5l9Yrcp3Xo4= +-----END CERTIFICATE----- diff --git a/tests/fixtures/api3/localhost.key b/tests/fixtures/api3/localhost.key new file mode 100644 index 00000000000..2486c15fefe --- /dev/null +++ b/tests/fixtures/api3/localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAoJ4FqoAlT6evM1RkywPWNhCmZzDz7q0br0pBjZeaoyxo+6mj +YCvBSVgPemYXUdN/0uSry7TuuVTIxpJJFitjnq8+sxYZIrkSWbiQXwM1Lhv/nkrB +kL5hAzRm4krBIzpZfgbsnBX3focokBP4Rz7w5+rcZDMD35njHbMzA0YLwCCG0+Ow +KTrw5EKav7l90J0LVHP2ET3Y2ah+Lx7WFRN5mYVE7HyFxhGHFhcshZXEAtz+3zJj +cxl+xG5ldw//c9qB4CfR5zGGqVYFjbcYNDHs5IRnFIQ+CrGcAdB9Bcis9GMyQU0d +4wzsdO8hMAZUJz33MQbcnLmFq2b5EBqN1YEfiwIDAQABAoIBADoh95sGVnrGDjtd +yD1SXi2jSRcAOMmiDesbzS4aOPXmFPlBJMiiDYsmPDPoz3fmPNVvvl40VlLtxN1a +BOnpOl0swFzBGsfehC3FBzvcRVsy9wmrtPNWdHZceQBeXhkJ/WoHx4uWx8Ub1iqP +j8T5mufVsX7yl+xOHk2ZllUQ/R/EEz9x00pkiH8Vsn8DhFI5KNqgi4n4c36T3vrn +MjTp+1o7bJ/cEnvXLi+IG2CO5y5hVbu3iKb+71YOGc6f/AJVzZ3MegC3KMFho9lh +DbDzumMuW8fZNyBfslXXoOr6oDqNq92n/jC/2hR8Xlth/aafisJiIVGydeVdDXhM +gDjdroECgYEAy3hXuo/Q1acncInGhIJvHjS/sVShP2epHz9zp8XuWl4NCuGP5V2c +jLT0hDW+ZKTUFweK9sQJNta81gs4pYc+2HGI8RP65XW4vgesNoKbBcE9xhEq0HMX +KN3/MJiwkNkM95T3nWqulhzNszhgNbZDMAU3Ule+o4n8udwOlFCTeXMCgYEAyhV4 +PoL3wp05BY0ssyKEqld3EqHNlPdQeJe1Dg9LSBy+3Z9sNngRD1/FuTo7RX6UY0FH +MaSI1JwhHSQ+2GNkqdMvVAilTXIDRw8vU9B77bYiHjny8+vMU06I9V3cJ57bNfmR +NUJtPmGO9xQ5UYxhP9rFOcI4MIecSzu1tvqiG4kCgYB01NoS7sdsFrnnvcS2i6rA +PmufqEeaf6w1nBqNyHJPg1eb2t7kRfdBOBp6291CLv71Zkhd3zynN3BguzrAmUL1 +x2Npgh57qTf2LbOt7RqUmFwfIfZikONIfQgt4E7qLSdr9iakRgCPg2R9ty5PSSOV +LDmS131IrE/obLoWYZn8jwKBgQDIaAxMahONA+CFueCHcgcA6yah6qZ3QeCjB0g9 +vjsZM7CxFqX5So8YoRDzxWT8YTCFUjppZ9NujbtlLAnLDJ7KsC2yd7R/Hj9T3CJC +S3JrZoFlWnCvJ7wFLdAzDTcEb8zTNUGlANBX2eYu7/Z8Aex7p9iJlCunLQV5sqhd +4yaaiQKBgQCERrz1XcJpM8S93nXdAv3Nn1bwA1V/ylx42DRxNEBl2JZQ1sQeqN36 +JvXPXhVZ3vTQDhVUqcVgqJIAb2xMviIVBnssOq3+pi/hOs13rakJf4AOulZ/3Si7 +HSLdymfQAMEKczU2261kw4pjPwiurkjAFWbQG2C8RGE/rR2y38PkDg== +-----END RSA PRIVATE KEY----- diff --git a/tests/fixtures/api3/utils.js b/tests/fixtures/api3/utils.js new file mode 100644 index 00000000000..942f948c10e --- /dev/null +++ b/tests/fixtures/api3/utils.js @@ -0,0 +1,21 @@ +'use strict'; + +function randomString (length, chars) { + let mask = ''; + if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz'; + if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + if (chars.indexOf('#') > -1) mask += '0123456789'; + if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; + + let result = ''; + + for (let i = length; i > 0; --i) + result += mask[Math.floor(Math.random() * mask.length)]; + + return result; +} + + +module.exports = { + randomString +}; \ No newline at end of file diff --git a/tests/fixtures/headless.js b/tests/fixtures/headless.js index 8b59b7b5dba..7706d5a8b0b 100644 --- a/tests/fixtures/headless.js +++ b/tests/fixtures/headless.js @@ -15,7 +15,7 @@ function headless (benv, binding) { var someData = opts.mockAjax || { }; benv.setup(function() { - benv.require(__dirname + '/../../tmp/js/bundle.js'); + benv.require(__dirname + '/../../tmp/js/bundle.report.js'); self.$ = $; diff --git a/tests/iob.test.js b/tests/iob.test.js index 30872e4fb4d..3b92fb8d05a 100644 --- a/tests/iob.test.js +++ b/tests/iob.test.js @@ -7,10 +7,11 @@ describe('IOB', function() { var ctx = {}; ctx.language = require('../lib/language')(); ctx.language.set('en'); + ctx.settings = require('../lib/settings')(); var iob = require('../lib/plugins/iob')(ctx); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var sbx = { properties: { @@ -20,14 +21,14 @@ describe('IOB', function() { } }; - iob.alexa.intentHandlers.length.should.equal(1); - iob.alexa.rollupHandlers.length.should.equal(1); + iob.virtAsst.intentHandlers.length.should.equal(1); + iob.virtAsst.rollupHandlers.length.should.equal(1); - iob.alexa.intentHandlers[0].intentHandler(function next(title, response) { + iob.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { title.should.equal('Current IOB'); response.should.equal('You have 1.50 units of insulin on board'); - iob.alexa.rollupHandlers[0].rollupHandler([], sbx, function callback (err, response) { + iob.virtAsst.rollupHandlers[0].rollupHandler([], sbx, function callback (err, response) { should.not.exist(err); response.results.should.equal('and you have 1.50 units of insulin on board.'); response.priority.should.equal(2); diff --git a/tests/loop.test.js b/tests/loop.test.js index 805f49040ab..bfe11d5075c 100644 --- a/tests/loop.test.js +++ b/tests/loop.test.js @@ -6,6 +6,7 @@ var moment = require('moment'); var ctx = { language: require('../lib/language')() + , settings: require('../lib/settings')() }; ctx.language.set('en'); var env = require('../env')(); @@ -119,7 +120,7 @@ describe('loop', function ( ) { , pluginBase: { updatePillText: function mockedUpdatePillText (plugin, options) { options.label.should.equal('Loop ⌁'); - options.value.should.equal('1m ago'); + options.value.should.equal('1m ago ↝ 147'); var first = _.first(options.info); first.label.should.equal('1m ago'); first.value.should.equal('Temp Basal Started 0.88U/hour for 30m, IOB: 0.17U, Predicted Min-Max BG: 147-149, Eventual BG: 147'); @@ -243,7 +244,7 @@ describe('loop', function ( ) { done(); }); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var ctx = { settings: { units: 'mg/dl' @@ -255,14 +256,14 @@ describe('loop', function ( ) { var sbx = sandbox.clientInit(ctx, now.valueOf(), {devicestatus: statuses}); loop.setProperties(sbx); - loop.alexa.intentHandlers.length.should.equal(2); + loop.virtAsst.intentHandlers.length.should.equal(2); - loop.alexa.intentHandlers[0].intentHandler(function next(title, response) { + loop.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { title.should.equal('Loop Forecast'); response.should.equal('According to the loop forecast you are expected to be between 147 and 149 over the next in 25 minutes'); - loop.alexa.intentHandlers[1].intentHandler(function next(title, response) { - title.should.equal('Last loop'); + loop.virtAsst.intentHandlers[1].intentHandler(function next(title, response) { + title.should.equal('Last Loop'); response.should.equal('The last successful loop was a few seconds ago'); done(); }, [], sbx); diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js deleted file mode 100644 index bbf801b7408..00000000000 --- a/tests/mqtt.test.js +++ /dev/null @@ -1,182 +0,0 @@ -'use strict'; - -var should = require('should'); - -var FIVE_MINS = 5 * 60 * 1000; - -describe('mqtt', function ( ) { - - var self = this; - - before(function () { - process.env.MQTT_MONITOR = 'mqtt://user:password@localhost:12345'; - process.env.STORAGE_URI='mongodb://localhost:27017/test_db'; - process.env.ENTRIES_COLLECTION='test_sgvs'; - self.env = require('../env')(); - self.es = require('event-stream'); - self.results = self.es.through(function (ch) { this.push(ch); }); - function outputs (fn) { - return self.es.writeArray(function (err, results) { - fn(err, results); - self.results.write(err || results); - }); - } - function written (data, fn) { - self.results.write(data); - setTimeout(fn, 5); - } - self.mqtt = require('../lib/server/mqtt')(self.env, {entries: { persist: outputs, create: written }, devicestatus: { create: written } }); - }); - - after(function () { - delete process.env.MQTT_MONITOR; - }); - - var now = Date.now() - , prev1 = now - FIVE_MINS - , prev2 = prev1 - FIVE_MINS - ; - - it('setup env correctly', function (done) { - self.env.mqtt_client_id.should.equal('nGVkio2g7p9+WOoiHB9YgmM'); - done(); - }); - - it('handle a download with only sgvs', function (done) { - var packet = { - sgv: [ - {sgv_mgdl: 110, trend: 4, date: prev2} - , {sgv_mgdl: 105, trend: 4, date: prev1} - , {sgv_mgdl: 100, trend: 4, date: now} - ] - }; - - var merged = self.mqtt.sgvSensorMerge(packet); - - merged.length.should.equal(packet.sgv.length); - - done(); - - }); - - it('merge sgvs and sensor records that match up', function (done) { - var packet = { - sgv: [ - {sgv_mgdl: 110, trend: 4, date: prev2} - , {sgv_mgdl: 105, trend: 4, date: prev1} - , {sgv_mgdl: 100, trend: 4, date: now} - ] - , sensor: [ - {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev2} - , {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev1} - , {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} - ] - }; - - var merged = self.mqtt.sgvSensorMerge(packet); - - merged.length.should.equal(packet.sgv.length); - - merged.filter(function (sgv) { - return sgv.filtered && sgv.unfiltered && sgv.rssi; - }).length.should.equal(packet.sgv.length); - - done(); - - }); - - it('downloadProtobuf should dispatch', function (done) { - - var payload = new Buffer('0a1108b70110d6d1fa6318f08df963200428011a1d323031352d30382d32335432323a35333a35352e3634392d30373a303020d7d1fa6328004a1508e0920b10c0850b18b20120d5d1fa6328ef8df963620a534d34313837393135306a053638393250', 'hex'); - - // var payload = self.mqtt.downloads.format(packet); - console.log('yaploda', '/downloads/protobuf', payload); - var l = [ ]; - self.results.on('data', function (chunk) { - l.push(chunk); - console.log('test data', l.length, chunk.length, chunk); - switch (l.length) { - case 0: // devicestatus - break; - case 2: // sgv - break; - case 3: // sgv - chunk.length.should.equal(1); - var first = chunk[0]; - should.exist(first.sgv); - should.exist(first.noise); - should.exist(first.date); - should.exist(first.dateString); - first.type.should.equal('sgv'); - break; - case 4: // cal - break; - case 1: // meter - break; - default: - break; - } - if (l.length >= 5) { - self.results.end( ); - } - }); - self.results.on('end', function ( ) { - done( ); - }); - self.mqtt.client.emit('message', '/downloads/protobuf', payload); - }); - - it('merge sgvs and sensor records that match up, and get the sgvs that don\'t match', function (done) { - var packet = { - sgv: [ - {sgv_mgdl: 110, trend: 4, date: prev2} - , {sgv_mgdl: 105, trend: 4, date: prev1} - , {sgv_mgdl: 100, trend: 4, date: now} - ] - , sensor: [ - {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} - ] - }; - - var merged = self.mqtt.sgvSensorMerge(packet); - - merged.length.should.equal(packet.sgv.length); - - var withBoth = merged.filter(function (sgv) { - return sgv.sgv && sgv.filtered && sgv.unfiltered && sgv.rssi; - }); - - withBoth.length.should.equal(1); - - done(); - - }); - - it('merge sgvs and sensor records that match up, and get the sensors that don\'t match', function (done) { - var packet = { - sgv: [ - {sgv_mgdl: 100, trend: 4, date: now} - ] - , sensor: [ - {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev2} - , {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev1} - , {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} - ] - }; - - var merged = self.mqtt.sgvSensorMerge(packet); - - merged.length.should.equal(packet.sensor.length); - - var withBoth = merged.filter(function (sgv) { - return sgv.sgv && sgv.filtered && sgv.unfiltered && sgv.rssi; - }); - - withBoth.length.should.equal(1); - - done(); - - }); - - -}); diff --git a/tests/openaps.test.js b/tests/openaps.test.js index ed3dd6d3b9f..b2e767c8fe2 100644 --- a/tests/openaps.test.js +++ b/tests/openaps.test.js @@ -6,6 +6,7 @@ var moment = require('moment'); var ctx = { language: require('../lib/language')() + , settings: require('../lib/settings')() }; ctx.language.set('en'); var env = require('../env')(); @@ -370,7 +371,7 @@ describe('openaps', function ( ) { done(); }); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var ctx = { settings: { units: 'mg/dl' @@ -382,14 +383,14 @@ describe('openaps', function ( ) { var sbx = sandbox.clientInit(ctx, now.valueOf(), {devicestatus: statuses}); openaps.setProperties(sbx); - openaps.alexa.intentHandlers.length.should.equal(2); + openaps.virtAsst.intentHandlers.length.should.equal(2); - openaps.alexa.intentHandlers[0].intentHandler(function next(title, response) { - title.should.equal('Loop Forecast'); + openaps.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { + title.should.equal('OpenAPS Forecast'); response.should.equal('The OpenAPS Eventual BG is 125'); - openaps.alexa.intentHandlers[1].intentHandler(function next(title, response) { - title.should.equal('Last loop'); + openaps.virtAsst.intentHandlers[1].intentHandler(function next(title, response) { + title.should.equal('Last Loop'); response.should.equal('The last successful loop was 2 minutes ago'); done(); }, [], sbx); diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js index d80a328648e..54946964012 100644 --- a/tests/pluginbase.test.js +++ b/tests/pluginbase.test.js @@ -4,7 +4,7 @@ require('should'); var benv = require('benv'); describe('pluginbase', function ( ) { - this.timeout(40000); // TODO: see why this test takes longer on Travis to complete + this.timeout(50000); // TODO: see why this test takes longer on Travis to complete var headless = require('./fixtures/headless')(benv, this); diff --git a/tests/profile.test.js b/tests/profile.test.js index 8171f459e3d..373f0479d9d 100644 --- a/tests/profile.test.js +++ b/tests/profile.test.js @@ -5,6 +5,10 @@ describe('Profile', function ( ) { var profile_empty = require('../lib/profilefunctions')(); + beforeEach(function() { + profile_empty.clear(); + }); + it('should say it does not have data before it has data', function() { var hasData = profile_empty.hasData(); hasData.should.equal(false); @@ -30,8 +34,6 @@ describe('Profile', function ( ) { }; var profile = require('../lib/profilefunctions')([profileData]); -// console.log(profile); - var now = Date.now(); it('should know what the DIA is with old style profiles', function() { diff --git a/tests/pump.test.js b/tests/pump.test.js index c6def822058..d051cbe7163 100644 --- a/tests/pump.test.js +++ b/tests/pump.test.js @@ -6,6 +6,7 @@ var moment = require('moment'); var ctx = { language: require('../lib/language')() + , settings: require('../lib/settings')() }; ctx.language.set('en'); var env = require('../env')(); @@ -254,7 +255,7 @@ describe('pump', function ( ) { done(); }); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var ctx = { settings: { units: 'mg/dl' @@ -266,16 +267,28 @@ describe('pump', function ( ) { var sbx = sandbox.clientInit(ctx, now.valueOf(), {devicestatus: statuses}); pump.setProperties(sbx); - pump.alexa.intentHandlers.length.should.equal(2); + pump.virtAsst.intentHandlers.length.should.equal(4); - pump.alexa.intentHandlers[0].intentHandler(function next(title, response) { - title.should.equal('Remaining insulin'); + pump.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { + title.should.equal('Insulin Remaining'); response.should.equal('You have 86.4 units remaining'); - pump.alexa.intentHandlers[1].intentHandler(function next(title, response) { - title.should.equal('Pump battery'); + pump.virtAsst.intentHandlers[1].intentHandler(function next(title, response) { + title.should.equal('Pump Battery'); response.should.equal('Your pump battery is at 1.52 volts'); - done(); + + pump.virtAsst.intentHandlers[2].intentHandler(function next(title, response) { + title.should.equal('Insulin Remaining'); + response.should.equal('You have 86.4 units remaining'); + + pump.virtAsst.intentHandlers[3].intentHandler(function next(title, response) { + title.should.equal('Pump Battery'); + response.should.equal('Your pump battery is at 1.52 volts'); + done(); + }, [], sbx); + + }, [], sbx); + }, [], sbx); }, [], sbx); diff --git a/tests/query.test.js b/tests/query.test.js new file mode 100644 index 00000000000..92a8a80673a --- /dev/null +++ b/tests/query.test.js @@ -0,0 +1,38 @@ +'use strict'; + +require('should'); + +var moment = require('moment'); + +describe('query', function ( ) { + var query = require('../lib/server/query'); + + it('should provide default options', function ( ) { + var opts = query(); + + var low = moment().utc().subtract(4, 'days').subtract(1, 'minutes').format(); + var high = moment().utc().subtract(4, 'days').add(1, 'minutes').format(); + + opts.date['$gte'].should.be.greaterThan(low); + opts.date['$gte'].should.be.lessThan(high); + }); + + it('should not override non default options', function ( ) { + var opts = query({}, { + deltaAgo: 2 * 24 * 60 * 60000, + dateField: 'created_at' + }); + + var low = moment().utc().subtract(2, 'days').subtract(1, 'minutes').format(); + var high = moment().utc().subtract(2, 'days').add(1, 'minutes').format(); + + opts.created_at['$gte'].should.greaterThan(low); + opts.created_at['$gte'].should.lessThan(high); + }); + + it('should not enforce date filter if query includes id', function ( ) { + var opts = query({ find: { _id: 1234 } }); + + (typeof opts.date).should.equal('undefined') + }); +}); diff --git a/tests/rawbg.test.js b/tests/rawbg.test.js index ab91d2bf722..48c21186cc5 100644 --- a/tests/rawbg.test.js +++ b/tests/rawbg.test.js @@ -35,16 +35,16 @@ describe('Raw BG', function ( ) { }); - it('should handle alexa requests', function (done) { + it('should handle virtAsst requests', function (done) { var sandbox = require('../lib/sandbox')(); var sbx = sandbox.clientInit(ctx, Date.now(), data); rawbg.setProperties(sbx); - rawbg.alexa.intentHandlers.length.should.equal(1); + rawbg.virtAsst.intentHandlers.length.should.equal(1); - rawbg.alexa.intentHandlers[0].intentHandler(function next(title, response) { + rawbg.virtAsst.intentHandlers[0].intentHandler(function next(title, response) { title.should.equal('Current Raw BG'); response.should.equal('Your raw bg is 113'); diff --git a/tests/reports.test.js b/tests/reports.test.js index d5ae4e80e35..7d5a0eb7009 100644 --- a/tests/reports.test.js +++ b/tests/reports.test.js @@ -261,10 +261,12 @@ describe('reports', function ( ) { var result = $('body').html(); //var filesys = require('fs'); //var logfile = filesys.createWriteStream('out.txt', { flags: 'a'} ) - //logfile.write($('body').html()); - + //logfile.write(result); + //console.log('RESULT', result); + result.indexOf('Milk now').should.be.greaterThan(-1); // daytoday - result.indexOf('50 g (1.67U)').should.be.greaterThan(-1); // daytoday + result.indexOf('50 g').should.be.greaterThan(-1); // daytoday + result.indexOf('TDD average: 2.9U').should.be.greaterThan(-1); // daytoday result.indexOf('
0%100%0%264.7%616 (100%)
@@ -118,9 +121,11 @@

Nightscout reporting Authentication status: - + - - + + + + diff --git a/views/translationsindex.html b/views/translationsindex.html index 9c394cc0374..4145f3d1db0 100644 --- a/views/translationsindex.html +++ b/views/translationsindex.html @@ -23,17 +23,21 @@ - - + + + <% include preloadCSS %> + +
X
+

Nightscout translations


Authentication status: - + diff --git a/webpack.config.js b/webpack.config.js index 9e781a3bf77..acd963d0b28 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,105 +1,176 @@ const path = require('path'); const webpack = require('webpack'); const MomentLocalesPlugin = require('moment-locales-webpack-plugin'); - - -var pluginArray = []; - -var sourceMapType = 'source-map'; - -if (process.env.NODE_ENV !== 'development') { +const pluginArray = []; +const sourceMapType = 'source-map'; +const TerserPlugin = require('terser-webpack-plugin'); +const MomentTimezoneDataPlugin = require('moment-timezone-data-webpack-plugin'); /* - console.log('Development environment detected, enabling Bundle Analyzer'); - - var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - - pluginArray.push(new BundleAnalyzerPlugin({ - // Can be `server`, `static` or `disabled`. - // In `server` mode analyzer will start HTTP server to show bundle report. - // In `static` mode single HTML file with bundle report will be generated. - // In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting `generateStatsFile` to `true`. - analyzerMode: 'static', - // Host that will be used in `server` mode to start HTTP server. - analyzerHost: '127.0.0.1', - // Port that will be used in `server` mode to start HTTP server. - analyzerPort: 8888, - // Path to bundle report file that will be generated in `static` mode. - // Relative to bundles output directory. - reportFilename: 'bundle_report.html', - // Module sizes to show in report by default. - // Should be one of `stat`, `parsed` or `gzip`. - // See "Definitions" section for more information. - defaultSizes: 'parsed', - // Automatically open report in default browser - openAnalyzer: true, - // If `true`, Webpack Stats JSON file will be generated in bundles output directory - generateStatsFile: false, - // Name of Webpack Stats JSON file that will be generated if `generateStatsFile` is `true`. - // Relative to bundles output directory. - statsFilename: 'stats.json', - // Options for `stats.toJson()` method. - // For example you can exclude sources of your modules from stats file with `source: false` option. - // See more options here: https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21 - statsOptions: null, - // Log level. Can be 'info', 'warn', 'error' or 'silent'. - logLevel: 'info' - })); -*/ +if (process.env.NODE_ENV === 'development') { + console.log('Development environment detected, enabling Bundle Analyzer'); + const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + pluginArray.push(new BundleAnalyzerPlugin({ + // Can be `server`, `static` or `disabled`. + // In `server` mode analyzer will start HTTP server to show bundle report. + // In `static` mode single HTML file with bundle report will be generated. + // In `disabled` mode you can use this plugin to just generate Webpack Stats JSON file by setting + // `generateStatsFile` to `true`. + analyzerMode: 'static', + // Host that will be used in `server` mode to start HTTP server. + analyzerHost: '127.0.0.1', + // Port that will be used in `server` mode to start HTTP server. + analyzerPort: 8888, + // Path to bundle report file that will be generated in `static` mode. + // Relative to bundles output directory. + reportFilename: 'bundle_report.html', + // Module sizes to show in report by default. + // Should be one of `stat`, `parsed` or `gzip`. + // See "Definitions" section for more information. + defaultSizes: 'parsed', + // Automatically open report in default browser + openAnalyzer: true, + // If `true`, Webpack Stats JSON file will be generated in bundles output directory + generateStatsFile: false, + // Name of Webpack Stats JSON file that will be generated if `generateStatsFile` is `true`. + // Relative to bundles output directory. + statsFilename: 'stats.json', + // Options for `stats.toJson()` method. + // For example you can exclude sources of your modules from stats file with `source: false` option. + // See more options here: https://github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21 + statsOptions: null, + // Log level. Can be 'info', 'warn', 'error' or 'silent'. + logLevel: 'info' + })); } +*/ -var jq = new webpack.ProvidePlugin({ - $: "jquery", - jQuery: "jquery", - "window.jQuery": "jquery'", - "window.$": "jquery" -}); +pluginArray.push(new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery', + 'window.jQuery': 'jquery', + 'window.$': 'jquery' +})); -pluginArray.push(jq); +// limit Timezone data from Moment + +pluginArray.push(new MomentTimezoneDataPlugin({ + startYear: 2010, + endYear: new Date().getFullYear() + 10, +})); // Strip all locales except the ones defined in lib/language.js // (“en” is built into Moment and can’t be removed, 'dk' is not defined in moment) - var momentLocales = new MomentLocalesPlugin({ - localesToKeep: ['bg', 'cs', 'de', 'el', 'es', 'fi', 'fr', 'he', 'hr', 'it', 'ko', 'nb', 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'sv', 'zh_cn', 'zh_tw'], - }) ; -pluginArray.push(momentLocales); - +pluginArray.push(new MomentLocalesPlugin({ + localesToKeep: ['bg', 'cs', 'de', 'el', 'es', 'fi', 'fr', 'he', 'hr', 'it', 'ko', 'nb', 'nl', 'pl', 'pt', 'ro', 'ru', + 'sk', 'sv', 'zh_cn', 'zh_tw' + ], +})); -module.exports = { - context: path.resolve(__dirname, '.'), - entry: { - app: './bundle/bundle.source.js' - }, - output: { - path: path.resolve(__dirname, './tmp'), - publicPath: '/', - filename: 'js/bundle.js', - sourceMapFilename: "js/bundle.js.map", +const rules = [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: { + loader: "babel-loader" + } + }, + { + test: /\.(jpe?g|png|gif)$/i, + loader: 'file-loader', + query: { + name: '[name].[ext]', + outputPath: 'images/' + //the images will be emmited to public/assets/images/ folder + //the images will be put in the DOM